Skip to content

Commit 5ecf80f

Browse files
dmealingclaude
andcommitted
chore(scripts): DB-free view-DDL dumper for projection round-trip checks
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 58dae06 commit 5ecf80f

1 file changed

Lines changed: 77 additions & 0 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// DB-free view-DDL dumper.
2+
//
3+
// Loads a MetaObjects metadata directory, finds projection entities
4+
// (a read-only `source.rdb @kind:view` with no writable source), and prints
5+
// the CREATE VIEW DDL each one compiles to — using the same pure functions the
6+
// CLI's `meta migrate` uses (`extractViewSpec` + `emitViewDdl`). No database.
7+
//
8+
// Usage:
9+
// bun scripts/dump-view-ddl.ts <metadataDir> -> all projections
10+
// bun scripts/dump-view-ddl.ts <metadataDir> <ViewName> -> just that one (by entity name OR view name)
11+
//
12+
// Mirrors the real call sequence in
13+
// packages/cli/src/lib/projection-migrations.ts (computeProjectionMigrations).
14+
import { MetaDataLoader, resolveTableName } from "@metaobjectsdev/metadata";
15+
import { isProjection, extractViewSpec, emitViewDdl } from "@metaobjectsdev/codegen-ts";
16+
17+
const dir = process.argv[2];
18+
const filter = process.argv[3];
19+
if (!dir) {
20+
console.error("need <metadataDir> [ViewNameOrEntityName]");
21+
process.exit(2);
22+
}
23+
24+
const res = await MetaDataLoader.fromDirectory(dir);
25+
if (res.errors.length) {
26+
console.error("LOAD ERRORS:", JSON.stringify(res.errors, null, 2));
27+
process.exit(1);
28+
}
29+
const root = res.root;
30+
31+
// Resolve table names for every writable entity, exactly as the CLI does, so
32+
// the emitter can map base entity + joined entities to physical tables.
33+
const joinTables: Record<string, string> = {};
34+
for (const obj of root.objects()) {
35+
joinTables[obj.name] = resolveTableName(obj);
36+
}
37+
38+
// Find projection entities (source.rdb @kind:view, read-only, no writable source).
39+
const projections = root.objects().filter(isProjection);
40+
41+
if (projections.length === 0) {
42+
console.error("-- no projection entities found");
43+
process.exit(0);
44+
}
45+
46+
let printed = 0;
47+
for (const projection of projections) {
48+
let spec: ReturnType<typeof extractViewSpec>;
49+
let createSql: string;
50+
try {
51+
spec = extractViewSpec(projection, root, { columnNamingStrategy: "snake_case" });
52+
const baseTableName = joinTables[spec.joinTree.baseEntity];
53+
if (!baseTableName) {
54+
console.log(`-- ${projection.name}: EMIT ERROR: base entity "${spec.joinTree.baseEntity}" has no resolvable table name`);
55+
continue;
56+
}
57+
createSql = emitViewDdl(spec, { dialect: "postgres", baseTableName, joinTables });
58+
} catch (e) {
59+
console.log(`-- ${projection.name}: EMIT ERROR: ${e instanceof Error ? e.message : String(e)}`);
60+
continue;
61+
}
62+
63+
// Filter (after a successful extract so we can match on the physical view name too).
64+
if (filter && projection.name !== filter && spec.viewName !== filter) {
65+
continue;
66+
}
67+
68+
if (printed > 0) console.log("");
69+
console.log(`-- projection: ${projection.name}`);
70+
console.log(createSql);
71+
printed++;
72+
}
73+
74+
if (filter && printed === 0) {
75+
console.error(`-- no projection matched "${filter}"`);
76+
process.exit(1);
77+
}

0 commit comments

Comments
 (0)