testTablesAndSchedulesByRole
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/); })