clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/141-demo-review-fixes
lofi / package / tooling / schema-manifest_test.ts
schema-manifest_test.ts 4.4K
1import { join } from "node:path";2import { pathToFileURL } from "node:url";3import { computeSchemaFingerprint, parseSchemaCompatManifest } from "../schema/compat.ts";4import { findSchemaAppExport, generateSchemaCompatManifest } from "./schema-manifest.ts";56function assert(condition: unknown, message: string): asserts condition {7 if (!condition) throw new Error(message);8}910const schemaModuleUrl = pathToFileURL(11 join(import.meta.dirname!, "..", "schema", "mod.ts"),12).href;1314async function makeProject(schemaSource: string): Promise<string> {15 const root = await Deno.makeTempDir({ dir: ".", prefix: ".lofi-schema-manifest-test-" });16 await Deno.mkdir(join(root, "src"), { recursive: true });17 await Deno.writeTextFile(join(root, "src", "schema.ts"), schemaSource);18 return root;19}2021const v1Source = `import { s } from ${JSON.stringify(schemaModuleUrl)};22export const app = s.defineApp({23 todos: s.table({ title: s.string(), done: s.boolean() }),24});25`;2627const v2Source = `import { s } from ${JSON.stringify(schemaModuleUrl)};28export const app = s.defineApp({29 todos: s.table({ title: s.string(), done: s.boolean(), note: s.string() }),30});31`;3233Deno.test("the build manifest stamps the schema head for the given revision", async () => {34 const root = await makeProject(v1Source);35 try {36 const manifest = await generateSchemaCompatManifest({ revision: "rev-1", root });37 assert(parseSchemaCompatManifest(manifest) !== null, "generated manifest failed to parse");38 assert(manifest.revision === "rev-1", "manifest lost the build revision");39 assert(/^v1:[0-9a-f]{16}$/.test(manifest.head), `manifest head is malformed: ${manifest.head}`);40 assert(41 manifest.lineage.length === 1 && manifest.lineage[0] === manifest.head,42 "an unmigrated project's lineage is not exactly its head",43 );44 } finally {45 await Deno.remove(root, { recursive: true });46 }47});4849Deno.test("committed migration snapshots become the manifest lineage", async () => {50 const root = await makeProject(v2Source);51 try {52 // The committed snapshot of the previous schema, as `migrations create`53 // writes it: the prior schema in wasm form.54 const previous = await makeProject(v1Source);55 let previousWasm: Record<string, unknown>;56 try {57 const moduleExports = await import(58 pathToFileURL(join(previous, "src", "schema.ts")).href59 ) as { app: { wasmSchema: Record<string, unknown> } };60 previousWasm = moduleExports.app.wasmSchema;61 } finally {62 await Deno.remove(previous, { recursive: true });63 }64 const snapshots = join(root, "src", "migrations", "snapshots");65 await Deno.mkdir(snapshots, { recursive: true });66 await Deno.writeTextFile(67 join(snapshots, "20260101T000000-abcdef123456.json"),68 `${JSON.stringify(previousWasm)}\n`,69 );70 const manifest = await generateSchemaCompatManifest({ revision: "rev-2", root });71 const ancestor = await computeSchemaFingerprint(previousWasm);72 assert(manifest.lineage.includes(manifest.head), "lineage omitted the head");73 assert(manifest.lineage.includes(ancestor), "lineage omitted the snapshot ancestor");74 assert(manifest.head !== ancestor, "the migrated schema collided with its ancestor");75 } finally {76 await Deno.remove(root, { recursive: true });77 }78});7980Deno.test("a schema module without an app value fails with an actionable error", async () => {81 const root = await makeProject(`export const notAnApp = { tables: [] };\n`);82 try {83 let message = "";84 try {85 await generateSchemaCompatManifest({ revision: "rev-3", root });86 } catch (error) {87 message = error instanceof Error ? error.message : String(error);88 }89 assert(message.includes("does not export an app value"), `unhelpful error: ${message}`);90 } finally {91 await Deno.remove(root, { recursive: true });92 }93});9495Deno.test("the app export is found by name or as the single app-shaped export", () => {96 const app = { wasmSchema: { todos: { columns: [] } } };97 assert(findSchemaAppExport({ app }) === app, "the app export was not preferred");98 assert(99 findSchemaAppExport({ v2: app, migration: { forward: [] } }) === app,100 "a single renamed app export was not found",101 );102 let message = "";103 try {104 findSchemaAppExport({ v1: app, v2: { wasmSchema: { notes: { columns: [] } } } });105 } catch (error) {106 message = error instanceof Error ? error.message : String(error);107 }108 assert(message.includes("several app values"), `ambiguity was not reported: ${message}`);109});