testTablesAndSchedulesByRole

tcmts.cli.test.e2e.data.testTablesAndSchedulesByRole · #dy4hgzgd · term

The server library's table and every, known by their roles (its manifest's roles, by hash) wherever it's mounted: here inside a package of the app's own (store.lib.server). A table declared with its table keeps its row type's schema, and a definition its every makes runs on its schedule. Code received from another codebase stays a table, though its library isn't named there when it's received.

import { plainEnv, tcmBin, withChildren } from "#codebase/tcmts/cli/test/e2e/harness";
import { codebase } from "#codebase/tcmts/cli/test/e2e/releases";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";

export const testTablesAndSchedulesByRole = () =>
  withChildren(async (keep) => {
    const alice = codebase("roles-a");
    alice.save(`// tcm: namespace store\nexport const manifest = { name: "store" };\n`, "add");
    alice.tcm("lib", "add", "tcm", "server", "--at", "store.lib.server");
    alice.save(
      `// tcm: namespace store.shop
import assert from "node:assert/strict";
import { every, table } from "#codebase/store/lib/server";
export type Item = { name: string; price: number };
export const items = table<Item>("items");
export const nightly = every("1d", () => undefined);
export async function main() {
  document.body.append("shop");
}
export const testRowsAreChecked = async () => {
  await items.put("a", { name: "apple", price: 1 });
  assert.equal((await items.get("a"))?.price, 1);
  await assert.rejects(items.put("b", { name: "pear" } as unknown as Item), /table "items": /);
};
`,
      "add",
    );
    // the table knows its row type: rows are checked against it
    assert.match(alice.tcm("test", "store.shop.testRowsAreChecked"), /1 passed, 0 failed/);

    // the schedule is found and run
    const child = keep(
      spawn(process.execPath, [tcmBin(), "start", "store.shop.main", "--port", "0"], {
        cwd: alice.dir,
        env: plainEnv(),
      }),
    );
    let out = "";
    child.stdout.setEncoding("utf8").on("data", (d: string) => (out += d));
    child.stderr.setEncoding("utf8").on("data", (d: string) => (out += d));
    for (const end = Date.now() + 60_000; !/jobs\s+/.test(out) && Date.now() < end;)
      await new Promise((r) => setTimeout(r, 100));
    assert.match(out, /jobs\s+recurring: store\.shop\.nightly \(1d\)/);
    child.kill();

    // received elsewhere, before the library is named there: still a table
    alice.tcm("release", "store@1.0.0");
    const bob = codebase("roles-b");
    bob.save(`// tcm: namespace app\nexport const greeting = "hi";\n`, "add");
    const at = /added (\S+): store@1\.0\.0/.exec(bob.tcm("lib", "add", alice.dir, "store"))![1]!;
    bob.save(
      `// tcm: namespace app
import assert from "node:assert/strict";
import { items } from "#codebase/${at.replaceAll(".", "/")}/shop";
export const testReceivedTablesKnowTheirRows = async () => {
  await items.put("a", { name: "apple", price: 1 });
  await assert.rejects(items.put("b", { name: "pear" } as never), /table "items": /);
};
`,
    );
    assert.match(bob.tcm("test", "app.testReceivedTablesKnowTheirRows"), /1 passed, 0 failed/);
  })

uses