On Your Own: Stock Gateway

The Course
•
4 min read

Build a stock gateway independently. Its transport returns Promise<unknown>. A valid payload is an array of records with a nonblank sku:string and finite nonnegative integer stock:number. Normalize sku before checking uniqueness. A single invalid row rejects the whole result.

Return Result<number>: success is the total stock, including valid zeros; failure has path and message. Transport rejection uses path transport. Non-list data uses list. A row problem uses an index-prefixed path such as 1.stock or 1.sku. Do not coerce numeric strings or mutate input data.

Test a normal payload, empty input, zero stock, text/negative/fractional stock, normalized duplicate sku, null and transport rejection. Types should enforce the async and result contracts; runtime tests must verify the data rules. Try the implementation before revealing the worked answer.

Predict, run, and explain

Write the output and trace the important state changes before checking it. Then run the program. Use Check types first; execution is allowed only when the real compiler accepts the source.

Predict, then runTypeScript
const stocks: readonly number[] = [0, 5];
let total = 0;

for (const stock of stocks) {
  total += stock;
}

console.log(total);

Enable JavaScript to edit and type-check this example.

Check your prediction
5

Your turn

Exercise

On Your Own: Stock Gateway: practise

Implement stockTotal(transport) with the contract above. Print 5 for valid stock, 1.stock for a bad second row, and transport for a rejected transport. Also include runtime assertions for empty, duplicate, null, negative, fractional and text-stock cases.

Your workspaceTypeScript
// Keep the transport catch narrow; validate the resulting unknown list afterward.

Enable JavaScript to edit and type-check this example.

Hint 1: the idea

Uniqueness must use normalized sku, not raw input spelling.

Hint 2: a first step

Track seen sku in Set<string>, accumulating only after each row has passed every check.

Show one solution
One possible solutionTypeScript
type Result<T> =
  | {
      ok: true;

      value: T;
    }
  | {
      ok: false;

      issue: {
        path: string;

        message: string;
      };
    };

async function stockTotal(
  transport: () => Promise<unknown>
): Promise<Result<number>> {
  let value: unknown;

  try {
    value = await transport();
  } catch {
    return { ok: false, issue: { path: "transport", message: "Unavailable" } };
  }

  if (!Array.isArray(value)) {
    return { ok: false, issue: { path: "list", message: "Expected array" } };
  }

  let total = 0;
  const seen = new Set<string>();

  for (const [index, element] of value.entries()) {
    const row: unknown = element;

    if (
      typeof row !== "object" ||
      row === null ||
      !("sku" in row) ||
      typeof row.sku !== "string" ||
      row.sku.trim() === "" ||
      seen.has(row.sku.trim())
    ) {
      return {
        ok: false,
        issue: { path: `${index}.sku`, message: "Invalid or duplicate sku" }
      };
    }

    if (
      !("stock" in row) ||
      typeof row.stock !== "number" ||
      !Number.isFinite(row.stock) ||
      !Number.isInteger(row.stock) ||
      row.stock < 0
    ) {
      return {
        ok: false,
        issue: { path: `${index}.stock`, message: "Invalid stock" }
      };
    }

    seen.add(row.sku.trim());
    total += row.stock;
  }

  return { ok: true, value: total };
}

async function expectIssue(value: unknown, path: string): Promise<void> {
  const result = await stockTotal(async () => value);

  if (result.ok || result.issue.path !== path) {
    throw new Error("Expected issue " + path);
  }
}

const empty = await stockTotal(async () => []);

if (!empty.ok || empty.value !== 0) {
  throw new Error("Empty contract failed");
}

for (const stock of [-1, 1.5, "0", Infinity]) {
  await expectIssue([{ sku: "a", stock }], "0.stock");
}

await expectIssue(
  [
    { sku: "a", stock: 0 },
    { sku: " a ", stock: 1 }
  ],
  "1.sku"
);
await expectIssue(null, "list");

const good = await stockTotal(async () => [
  { sku: "a", stock: 0 },
  { sku: "b", stock: 5 }
]);
const bad = await stockTotal(async () => [
  { sku: "a", stock: 0 },
  { sku: "b", stock: "5" }
]);
const failed = await stockTotal(async () => {
  throw "offline";
});

console.log(good.ok ? good.value : good.issue.path);
console.log(bad.ok ? "unexpected" : bad.issue.path);
console.log(failed.ok ? "unexpected" : failed.issue.path);

Enable JavaScript to edit and type-check this example.

5
1.stock
transport

Explain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?

Change it and test it

Add nonmutation and missing-field tests. Temporarily remove the normalized duplicate check and confirm a test fails. Then decide whether your domain should reject totals exceeding Number.MAX_SAFE_INTEGER: per-row integer validity does not guarantee aggregate precision, so add an explicit bound if exact large counts are required.

Was this article helpful?

Your response is saved on this device.