|
| 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