@metaobjectsdev/migrate-ts 0.11.4 → 0.11.5-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/diff/index.js +43 -1
  2. package/dist/diff/index.js.map +1 -1
  3. package/dist/drift/drift.d.ts +6 -0
  4. package/dist/drift/drift.d.ts.map +1 -1
  5. package/dist/drift/drift.js +1 -0
  6. package/dist/drift/drift.js.map +1 -1
  7. package/dist/emit/index.d.ts.map +1 -1
  8. package/dist/emit/index.js +0 -9
  9. package/dist/emit/index.js.map +1 -1
  10. package/dist/emit/sqlite.d.ts.map +1 -1
  11. package/dist/emit/sqlite.js +21 -9
  12. package/dist/emit/sqlite.js.map +1 -1
  13. package/dist/expected-schema.d.ts +9 -1
  14. package/dist/expected-schema.d.ts.map +1 -1
  15. package/dist/expected-schema.js +4 -5
  16. package/dist/expected-schema.js.map +1 -1
  17. package/dist/index.d.ts +0 -5
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +4 -6
  20. package/dist/index.js.map +1 -1
  21. package/dist/introspect/d1.js +10 -2
  22. package/dist/introspect/d1.js.map +1 -1
  23. package/dist/snapshot/plan.d.ts +4 -2
  24. package/dist/snapshot/plan.d.ts.map +1 -1
  25. package/dist/snapshot/plan.js +3 -1
  26. package/dist/snapshot/plan.js.map +1 -1
  27. package/dist/types.d.ts +8 -0
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/view-sql-compare.d.ts.map +1 -1
  30. package/dist/view-sql-compare.js +5 -7
  31. package/dist/view-sql-compare.js.map +1 -1
  32. package/package.json +2 -2
  33. package/src/diff/index.ts +47 -1
  34. package/src/drift/drift.ts +7 -0
  35. package/src/emit/index.ts +3 -13
  36. package/src/emit/sqlite.ts +23 -10
  37. package/src/expected-schema.ts +13 -6
  38. package/src/index.ts +4 -13
  39. package/src/introspect/d1.ts +9 -2
  40. package/src/snapshot/plan.ts +6 -1
  41. package/src/types.ts +8 -0
  42. package/src/view-sql-compare.ts +5 -7
  43. package/dist/expected-views.d.ts +0 -5
  44. package/dist/expected-views.d.ts.map +0 -1
  45. package/dist/expected-views.js +0 -169
  46. package/dist/expected-views.js.map +0 -1
  47. package/dist/source-aware-diff.d.ts +0 -20
  48. package/dist/source-aware-diff.d.ts.map +0 -1
  49. package/dist/source-aware-diff.js +0 -24
  50. package/dist/source-aware-diff.js.map +0 -1
  51. package/dist/view-ddl-postgres.d.ts +0 -4
  52. package/dist/view-ddl-postgres.d.ts.map +0 -1
  53. package/dist/view-ddl-postgres.js +0 -13
  54. package/dist/view-ddl-postgres.js.map +0 -1
  55. package/dist/view-ddl-sqlite.d.ts +0 -3
  56. package/dist/view-ddl-sqlite.d.ts.map +0 -1
  57. package/dist/view-ddl-sqlite.js +0 -7
  58. package/dist/view-ddl-sqlite.js.map +0 -1
  59. package/dist/view-diff.d.ts +0 -13
  60. package/dist/view-diff.d.ts.map +0 -1
  61. package/dist/view-diff.js +0 -42
  62. package/dist/view-diff.js.map +0 -1
  63. package/src/expected-views.ts +0 -195
  64. package/src/source-aware-diff.ts +0 -49
  65. package/src/view-ddl-postgres.ts +0 -15
  66. package/src/view-ddl-sqlite.ts +0 -7
  67. package/src/view-diff.ts +0 -55
@@ -1,169 +0,0 @@
1
- // expected-views.ts — derive ViewDescriptor[] from projection metadata.
2
- //
3
- // Ports the shape of csharp/MetaObjects.Codegen/Schema/PostgresSchema.cs's
4
- // CreateView. v1 supports:
5
- //
6
- // * passthrough origin (no @via) → plain column from the single base entity
7
- // * aggregate origin (@agg/@of/@via) → correlated subquery over a to-many
8
- //
9
- // Deferred to v2 (returns a "-- TODO" view that the runner skips applying):
10
- //
11
- // * passthrough origin WITH @via → to-one correlated subquery
12
- // * collection origin → json_agg over a to-many
13
- // * multi-base projections → blocked
14
- //
15
- // Identifiers are quoted throughout so mixed-case column names (e.g.
16
- // "programId") survive PG's case-folding pass.
17
- import { MetaPassthroughOrigin, MetaAggregateOrigin, MetaCollectionOrigin, MetaSource, SOURCE_ROLE_PRIMARY, TYPE_OBJECT, TYPE_ORIGIN, resolveColumnName, resolveTableName, stripPackage, } from "@metaobjectsdev/metadata";
18
- export function buildExpectedViews(root, strategy) {
19
- const out = [];
20
- for (const child of root.ownChildren()) {
21
- if (child.type !== TYPE_OBJECT)
22
- continue;
23
- const proj = child;
24
- if (proj.isAbstract)
25
- continue;
26
- if (!isReadOnlyProjection(proj))
27
- continue;
28
- const view = buildView(proj, root, strategy);
29
- if (view !== null)
30
- out.push(view);
31
- }
32
- return out;
33
- }
34
- function isReadOnlyProjection(entity) {
35
- const sources = entity.ownChildren().filter((c) => c instanceof MetaSource);
36
- if (sources.length === 0)
37
- return false;
38
- const hasReadOnly = sources.some((s) => s.isReadOnly());
39
- const hasWritable = sources.some((s) => !s.isReadOnly());
40
- return hasReadOnly && !hasWritable;
41
- }
42
- function buildView(projection, root, strategy) {
43
- const primarySource = projection.ownChildren().find((c) => c instanceof MetaSource && c.role === SOURCE_ROLE_PRIMARY);
44
- // Use physicalName (FR-016 kind-aware), NOT tableName — a view-kind source names
45
- // itself via @view, and tableName reads ONLY the @table slot (undefined for views),
46
- // which previously bailed every @view projection here before any column was built.
47
- if (primarySource === undefined)
48
- return null;
49
- const viewName = primarySource.physicalName;
50
- const cols = [];
51
- let baseEntity;
52
- let blocked;
53
- const T = "t"; // target alias in correlated subqueries
54
- const baseTable = () => {
55
- if (baseEntity === undefined)
56
- return undefined;
57
- const found = root.findObject(baseEntity);
58
- return found ? resolveTableName(found) : baseEntity;
59
- };
60
- for (const f of projection.fields()) {
61
- const origin = f.ownChildren().find((c) => c.type === TYPE_ORIGIN);
62
- const fieldCol = resolveColumnName(f, strategy);
63
- // FR-024 base-link: a projection field that uses `extends: Base.field` (instead of
64
- // an explicit origin) is an implicit passthrough — its source IS the extended field.
65
- // That linkage lives in EXTENDED metadata (superRef / resolveSuper()), which
66
- // f.ownChildren() cannot see, so without this an extends-based field (typically the
67
- // PK that declares the projection's base) falls through to "no resolvable origin" and
68
- // bails the whole view — taking the sibling origin.passthrough renames down with it.
69
- if (origin === undefined && f.superRef !== undefined && f.superRef.includes(".")) {
70
- const [ent, field] = splitDot(f.superRef);
71
- const bare = stripPackage(ent);
72
- baseEntity ??= bare;
73
- if (bare !== baseEntity) {
74
- blocked = "passthrough from multiple base entities";
75
- break;
76
- }
77
- const srcEntity = root.findObject(baseEntity);
78
- const srcCol = resolveColumnByName(srcEntity, field, strategy);
79
- cols.push(` "${srcCol}" AS "${fieldCol}"`);
80
- continue;
81
- }
82
- if (origin instanceof MetaPassthroughOrigin && origin.via === undefined &&
83
- origin.from !== undefined && origin.from.includes(".")) {
84
- const [ent, field] = splitDot(origin.from);
85
- const bare = stripPackage(ent);
86
- baseEntity ??= bare;
87
- if (bare !== baseEntity) {
88
- blocked = "passthrough from multiple base entities";
89
- break;
90
- }
91
- const srcEntity = root.findObject(baseEntity);
92
- const srcCol = resolveColumnByName(srcEntity, field, strategy);
93
- cols.push(` "${srcCol}" AS "${fieldCol}"`);
94
- }
95
- else if (origin instanceof MetaAggregateOrigin && origin.agg !== undefined &&
96
- origin.of !== undefined && origin.via !== undefined &&
97
- origin.of.includes(".") && origin.via.includes(".")) {
98
- const [baseEnt, relName] = splitDot(origin.via);
99
- const bareBase = stripPackage(baseEnt);
100
- baseEntity ??= bareBase;
101
- if (bareBase !== baseEntity) {
102
- blocked = "aggregate over a different base entity";
103
- break;
104
- }
105
- const fk = resolveToManyFk(root, baseEntity, relName, strategy);
106
- if (fk === null) {
107
- blocked = `unresolved to-many FK for @via "${origin.via}" (target needs an identity.reference back to ${baseEntity})`;
108
- break;
109
- }
110
- const ofCol = resolveColumnByName(fk.target, splitDot(origin.of)[1], strategy);
111
- cols.push(` (SELECT ${origin.agg}(${T}."${ofCol}") ` +
112
- `FROM "${fk.targetTable}" ${T} ` +
113
- `WHERE ${T}."${fk.fkCol}" = "${baseTable()}"."${fk.parentCol}") AS "${fieldCol}"`);
114
- }
115
- else if (origin instanceof MetaPassthroughOrigin || origin instanceof MetaCollectionOrigin) {
116
- blocked = `field "${f.name}" uses an origin shape not yet supported by TS migrate-ts (passthrough-via / collection — deferred)`;
117
- break;
118
- }
119
- else {
120
- blocked = `field "${f.name}" has no resolvable origin`;
121
- break;
122
- }
123
- }
124
- if (blocked !== undefined || baseEntity === undefined || cols.length === 0) {
125
- // Caller decides what to do with a null result. For v1 the runner just
126
- // omits it from expected.views — diff sees no expected view, no
127
- // create-view change, and an actual leftover view (if any) gets dropped.
128
- return null;
129
- }
130
- const sql = `SELECT\n${cols.join(",\n")}\nFROM "${baseTable()}"`;
131
- const view = { name: viewName, sql };
132
- return view;
133
- }
134
- function splitDot(s) {
135
- const i = s.indexOf(".");
136
- return [s.slice(0, i), s.slice(i + 1)];
137
- }
138
- function resolveColumnByName(owner, fieldName, strategy) {
139
- if (owner === undefined)
140
- return fieldName;
141
- const field = owner.fields().find((f) => f.name === fieldName);
142
- return field ? resolveColumnName(field, strategy) : fieldName;
143
- }
144
- function resolveToManyFk(root, baseEntityName, relName, strategy) {
145
- const baseObj = root.findObject(baseEntityName);
146
- if (baseObj === undefined)
147
- return null;
148
- const rel = baseObj.relationships().find((r) => r.name === relName);
149
- if (rel === undefined || rel.objectRef === undefined)
150
- return null;
151
- const target = root.findObject(stripPackage(rel.objectRef));
152
- if (target === undefined)
153
- return null;
154
- const fkRef = target.referenceIdentities().find((r) => r.targetEntity !== undefined && stripPackage(r.targetEntity) === baseEntityName);
155
- if (fkRef === undefined)
156
- return null;
157
- const fkFields = fkRef.fields;
158
- if (fkFields.length === 0)
159
- return null;
160
- const fkCol = resolveColumnByName(target, fkFields[0], strategy);
161
- const parentFieldName = fkRef.targetFields.length > 0
162
- ? fkRef.targetFields[0]
163
- : baseObj.primaryIdentity()?.fields[0];
164
- if (parentFieldName === undefined)
165
- return null;
166
- const parentCol = resolveColumnByName(baseObj, parentFieldName, strategy);
167
- return { target, targetTable: resolveTableName(target), fkCol, parentCol };
168
- }
169
- //# sourceMappingURL=expected-views.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"expected-views.js","sourceRoot":"","sources":["../src/expected-views.ts"],"names":[],"mappings":"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"}
@@ -1,20 +0,0 @@
1
- import { type ViewShape } from "./view-diff.js";
2
- export interface ViewMigrationInput {
3
- readonly viewName: string;
4
- /** Previous view shape (from migration log / prior generation). undefined = new view. */
5
- readonly prevShape?: ViewShape;
6
- readonly nextShape: ViewShape;
7
- /** Full `CREATE VIEW ... AS ...;` SQL for nextShape. */
8
- readonly createSql: string;
9
- }
10
- export interface ViewMigrationsOpts {
11
- readonly dialect: "postgres" | "sqlite";
12
- readonly allowBreaking: boolean;
13
- readonly views: readonly ViewMigrationInput[];
14
- }
15
- export interface ViewMigrationsResult {
16
- readonly migrations: readonly string[];
17
- readonly errors: readonly string[];
18
- }
19
- export declare function computeViewMigrations(opts: ViewMigrationsOpts): ViewMigrationsResult;
20
- //# sourceMappingURL=source-aware-diff.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"source-aware-diff.d.ts","sourceRoot":"","sources":["../src/source-aware-diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAIlE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,QAAQ,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC/C;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,kBAAkB,GAAG,oBAAoB,CAwBpF"}
@@ -1,24 +0,0 @@
1
- import { classifyViewDiff } from "./view-diff.js";
2
- import { emitPostgresViewMigration } from "./view-ddl-postgres.js";
3
- import { emitSqliteViewMigration } from "./view-ddl-sqlite.js";
4
- export function computeViewMigrations(opts) {
5
- const migrations = [];
6
- const errors = [];
7
- for (const view of opts.views) {
8
- const diffClass = view.prevShape
9
- ? classifyViewDiff(view.prevShape, view.nextShape)
10
- : "safe-append"; // new view = treated like safe-append
11
- if (diffClass === "breaking" && !opts.allowBreaking) {
12
- errors.push(`View ${view.viewName} has a breaking change. Pass --allow-breaking to allow drop+recreate.`);
13
- continue;
14
- }
15
- const emit = opts.dialect === "postgres"
16
- ? emitPostgresViewMigration
17
- : emitSqliteViewMigration;
18
- const sql = emit({ diffClass, viewName: view.viewName, createSql: view.createSql });
19
- if (sql)
20
- migrations.push(sql);
21
- }
22
- return { migrations, errors };
23
- }
24
- //# sourceMappingURL=source-aware-diff.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"source-aware-diff.js","sourceRoot":"","sources":["../src/source-aware-diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAkB,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAsB/D,MAAM,UAAU,qBAAqB,CAAC,IAAwB;IAC5D,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS;YAC9B,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC;YAClD,CAAC,CAAC,aAAa,CAAC,CAAE,sCAAsC;QAE1D,IAAI,SAAS,KAAK,UAAU,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACpD,MAAM,CAAC,IAAI,CACT,QAAQ,IAAI,CAAC,QAAQ,uEAAuE,CAC7F,CAAC;YACF,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,KAAK,UAAU;YACtC,CAAC,CAAC,yBAAyB;YAC3B,CAAC,CAAC,uBAAuB,CAAC;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QACpF,IAAI,GAAG;YAAE,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AAChC,CAAC"}
@@ -1,4 +0,0 @@
1
- import type { ViewMigrationOpts } from "./view-diff.js";
2
- export { type ViewMigrationOpts } from "./view-diff.js";
3
- export declare function emitPostgresViewMigration(opts: ViewMigrationOpts): string;
4
- //# sourceMappingURL=view-ddl-postgres.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"view-ddl-postgres.d.ts","sourceRoot":"","sources":["../src/view-ddl-postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAUzE"}
@@ -1,13 +0,0 @@
1
- export {} from "./view-diff.js";
2
- export function emitPostgresViewMigration(opts) {
3
- switch (opts.diffClass) {
4
- case "no-change": return "";
5
- case "safe-append":
6
- case "safe-replace":
7
- // Replace CREATE VIEW with CREATE OR REPLACE VIEW.
8
- return opts.createSql.replace(/^CREATE VIEW\b/i, "CREATE OR REPLACE VIEW");
9
- case "breaking":
10
- return `DROP VIEW IF EXISTS ${opts.viewName} CASCADE;\n${opts.createSql}`;
11
- }
12
- }
13
- //# sourceMappingURL=view-ddl-postgres.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"view-ddl-postgres.js","sourceRoot":"","sources":["../src/view-ddl-postgres.ts"],"names":[],"mappings":"AAEA,OAAO,EAA0B,MAAM,gBAAgB,CAAC;AAExD,MAAM,UAAU,yBAAyB,CAAC,IAAuB;IAC/D,QAAQ,IAAI,CAAC,SAAS,EAAE,CAAC;QACvB,KAAK,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC;QAC5B,KAAK,aAAa,CAAC;QACnB,KAAK,cAAc;YACjB,mDAAmD;YACnD,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,wBAAwB,CAAC,CAAC;QAC7E,KAAK,UAAU;YACb,OAAO,uBAAuB,IAAI,CAAC,QAAQ,cAAc,IAAI,CAAC,SAAS,EAAE,CAAC;IAC9E,CAAC;AACH,CAAC"}
@@ -1,3 +0,0 @@
1
- import type { ViewMigrationOpts } from "./view-diff.js";
2
- export declare function emitSqliteViewMigration(opts: ViewMigrationOpts): string;
3
- //# sourceMappingURL=view-ddl-sqlite.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"view-ddl-sqlite.d.ts","sourceRoot":"","sources":["../src/view-ddl-sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAIvE"}
@@ -1,7 +0,0 @@
1
- export function emitSqliteViewMigration(opts) {
2
- if (opts.diffClass === "no-change")
3
- return "";
4
- // SQLite has no CREATE OR REPLACE VIEW; must drop+create. Wrap in txn.
5
- return `BEGIN;\nDROP VIEW IF EXISTS ${opts.viewName};\n${opts.createSql}\nCOMMIT;`;
6
- }
7
- //# sourceMappingURL=view-ddl-sqlite.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"view-ddl-sqlite.js","sourceRoot":"","sources":["../src/view-ddl-sqlite.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,uBAAuB,CAAC,IAAuB;IAC7D,IAAI,IAAI,CAAC,SAAS,KAAK,WAAW;QAAE,OAAO,EAAE,CAAC;IAC9C,uEAAuE;IACvE,OAAO,+BAA+B,IAAI,CAAC,QAAQ,MAAM,IAAI,CAAC,SAAS,WAAW,CAAC;AACrF,CAAC"}
@@ -1,13 +0,0 @@
1
- export interface ViewShape {
2
- readonly columns: readonly string[];
3
- readonly columnTypes?: Readonly<Record<string, string>>;
4
- }
5
- export type ViewDiffClass = "no-change" | "safe-append" | "safe-replace" | "breaking";
6
- export interface ViewMigrationOpts {
7
- readonly diffClass: ViewDiffClass;
8
- readonly viewName: string;
9
- /** Full `CREATE VIEW <name> AS ...;` statement (semicolon included). */
10
- readonly createSql: string;
11
- }
12
- export declare function classifyViewDiff(prev: ViewShape, next: ViewShape): ViewDiffClass;
13
- //# sourceMappingURL=view-diff.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"view-diff.d.ts","sourceRoot":"","sources":["../src/view-diff.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACzD;AAED,MAAM,MAAM,aAAa,GACrB,WAAW,GACX,aAAa,GACb,cAAc,GACd,UAAU,CAAC;AAEf,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,aAAa,CAoChF"}
package/dist/view-diff.js DELETED
@@ -1,42 +0,0 @@
1
- export function classifyViewDiff(prev, next) {
2
- const prevCols = prev.columns;
3
- const nextCols = next.columns;
4
- // Dropped column = breaking.
5
- for (const c of prevCols) {
6
- if (!nextCols.includes(c))
7
- return "breaking";
8
- }
9
- // Type change on existing column = breaking.
10
- if (prev.columnTypes && next.columnTypes) {
11
- for (const c of prevCols) {
12
- const pt = prev.columnTypes[c];
13
- const nt = next.columnTypes[c];
14
- if (pt && nt && pt !== nt)
15
- return "breaking";
16
- }
17
- }
18
- if (prevCols.length === nextCols.length) {
19
- // Same length, same set → either identical order or reordered.
20
- let identical = true;
21
- for (let i = 0; i < prevCols.length; i++) {
22
- if (prevCols[i] !== nextCols[i]) {
23
- identical = false;
24
- break;
25
- }
26
- }
27
- return identical ? "no-change" : "safe-replace";
28
- }
29
- // Longer next, same prefix? → safe-append.
30
- let prefixMatches = true;
31
- for (let i = 0; i < prevCols.length; i++) {
32
- if (prevCols[i] !== nextCols[i]) {
33
- prefixMatches = false;
34
- break;
35
- }
36
- }
37
- if (prefixMatches)
38
- return "safe-append";
39
- // Mixed: reordered + appended → safe-replace (data shape unchanged at column level).
40
- return "safe-replace";
41
- }
42
- //# sourceMappingURL=view-diff.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"view-diff.js","sourceRoot":"","sources":["../src/view-diff.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,gBAAgB,CAAC,IAAe,EAAE,IAAe;IAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC;IAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC;IAE9B,6BAA6B;IAC7B,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,OAAO,UAAU,CAAC;IAC/C,CAAC;IAED,6CAA6C;IAC7C,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YAC/B,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE;gBAAE,OAAO,UAAU,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;QACxC,+DAA+D;QAC/D,IAAI,SAAS,GAAG,IAAI,CAAC;QACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;gBAAC,SAAS,GAAG,KAAK,CAAC;gBAAC,MAAM;YAAC,CAAC;QAChE,CAAC;QACD,OAAO,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC;IAClD,CAAC;IAED,2CAA2C;IAC3C,IAAI,aAAa,GAAG,IAAI,CAAC;IACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YAAC,aAAa,GAAG,KAAK,CAAC;YAAC,MAAM;QAAC,CAAC;IACpE,CAAC;IACD,IAAI,aAAa;QAAE,OAAO,aAAa,CAAC;IAExC,qFAAqF;IACrF,OAAO,cAAc,CAAC;AACxB,CAAC"}
@@ -1,195 +0,0 @@
1
- // expected-views.ts — derive ViewDescriptor[] from projection metadata.
2
- //
3
- // Ports the shape of csharp/MetaObjects.Codegen/Schema/PostgresSchema.cs's
4
- // CreateView. v1 supports:
5
- //
6
- // * passthrough origin (no @via) → plain column from the single base entity
7
- // * aggregate origin (@agg/@of/@via) → correlated subquery over a to-many
8
- //
9
- // Deferred to v2 (returns a "-- TODO" view that the runner skips applying):
10
- //
11
- // * passthrough origin WITH @via → to-one correlated subquery
12
- // * collection origin → json_agg over a to-many
13
- // * multi-base projections → blocked
14
- //
15
- // Identifiers are quoted throughout so mixed-case column names (e.g.
16
- // "programId") survive PG's case-folding pass.
17
-
18
- import type { MetaObject, MetaRoot } from "@metaobjectsdev/metadata";
19
- import {
20
- type ColumnNamingStrategy,
21
- MetaPassthroughOrigin,
22
- MetaAggregateOrigin,
23
- MetaCollectionOrigin,
24
- MetaSource,
25
- SOURCE_ROLE_PRIMARY,
26
- TYPE_OBJECT,
27
- TYPE_ORIGIN,
28
- resolveColumnName,
29
- resolveTableName,
30
- stripPackage,
31
- } from "@metaobjectsdev/metadata";
32
- import type { ViewDescriptor } from "./types.js";
33
-
34
- export function buildExpectedViews(root: MetaRoot, strategy: ColumnNamingStrategy): ViewDescriptor[] {
35
- const out: ViewDescriptor[] = [];
36
- for (const child of root.ownChildren()) {
37
- if (child.type !== TYPE_OBJECT) continue;
38
- const proj = child as MetaObject;
39
- if (proj.isAbstract) continue;
40
- if (!isReadOnlyProjection(proj)) continue;
41
- const view = buildView(proj, root, strategy);
42
- if (view !== null) out.push(view);
43
- }
44
- return out;
45
- }
46
-
47
- function isReadOnlyProjection(entity: MetaObject): boolean {
48
- const sources = entity.ownChildren().filter((c): c is MetaSource => c instanceof MetaSource);
49
- if (sources.length === 0) return false;
50
- const hasReadOnly = sources.some((s) => s.isReadOnly());
51
- const hasWritable = sources.some((s) => !s.isReadOnly());
52
- return hasReadOnly && !hasWritable;
53
- }
54
-
55
- function buildView(
56
- projection: MetaObject, root: MetaRoot, strategy: ColumnNamingStrategy,
57
- ): ViewDescriptor | null {
58
- const primarySource = projection.ownChildren().find(
59
- (c): c is MetaSource => c instanceof MetaSource && c.role === SOURCE_ROLE_PRIMARY,
60
- );
61
- // Use physicalName (FR-016 kind-aware), NOT tableName — a view-kind source names
62
- // itself via @view, and tableName reads ONLY the @table slot (undefined for views),
63
- // which previously bailed every @view projection here before any column was built.
64
- if (primarySource === undefined) return null;
65
- const viewName = primarySource.physicalName;
66
-
67
- const cols: string[] = [];
68
- let baseEntity: string | undefined;
69
- let blocked: string | undefined;
70
- const T = "t"; // target alias in correlated subqueries
71
-
72
- const baseTable = () => {
73
- if (baseEntity === undefined) return undefined;
74
- const found = root.findObject(baseEntity);
75
- return found ? resolveTableName(found) : baseEntity;
76
- };
77
-
78
- for (const f of projection.fields()) {
79
- const origin = f.ownChildren().find((c) => c.type === TYPE_ORIGIN);
80
- const fieldCol = resolveColumnName(f, strategy);
81
-
82
- // FR-024 base-link: a projection field that uses `extends: Base.field` (instead of
83
- // an explicit origin) is an implicit passthrough — its source IS the extended field.
84
- // That linkage lives in EXTENDED metadata (superRef / resolveSuper()), which
85
- // f.ownChildren() cannot see, so without this an extends-based field (typically the
86
- // PK that declares the projection's base) falls through to "no resolvable origin" and
87
- // bails the whole view — taking the sibling origin.passthrough renames down with it.
88
- if (origin === undefined && f.superRef !== undefined && f.superRef.includes(".")) {
89
- const [ent, field] = splitDot(f.superRef);
90
- const bare = stripPackage(ent);
91
- baseEntity ??= bare;
92
- if (bare !== baseEntity) { blocked = "passthrough from multiple base entities"; break; }
93
- const srcEntity = root.findObject(baseEntity);
94
- const srcCol = resolveColumnByName(srcEntity, field, strategy);
95
- cols.push(` "${srcCol}" AS "${fieldCol}"`);
96
- continue;
97
- }
98
-
99
- if (origin instanceof MetaPassthroughOrigin && origin.via === undefined &&
100
- origin.from !== undefined && origin.from.includes(".")) {
101
- const [ent, field] = splitDot(origin.from);
102
- const bare = stripPackage(ent);
103
- baseEntity ??= bare;
104
- if (bare !== baseEntity) { blocked = "passthrough from multiple base entities"; break; }
105
- const srcEntity = root.findObject(baseEntity);
106
- const srcCol = resolveColumnByName(srcEntity, field, strategy);
107
- cols.push(` "${srcCol}" AS "${fieldCol}"`);
108
- } else if (origin instanceof MetaAggregateOrigin && origin.agg !== undefined &&
109
- origin.of !== undefined && origin.via !== undefined &&
110
- origin.of.includes(".") && origin.via.includes(".")) {
111
- const [baseEnt, relName] = splitDot(origin.via);
112
- const bareBase = stripPackage(baseEnt);
113
- baseEntity ??= bareBase;
114
- if (bareBase !== baseEntity) { blocked = "aggregate over a different base entity"; break; }
115
- const fk = resolveToManyFk(root, baseEntity, relName, strategy);
116
- if (fk === null) {
117
- blocked = `unresolved to-many FK for @via "${origin.via}" (target needs an identity.reference back to ${baseEntity})`;
118
- break;
119
- }
120
- const ofCol = resolveColumnByName(fk.target, splitDot(origin.of)[1], strategy);
121
- cols.push(
122
- ` (SELECT ${origin.agg}(${T}."${ofCol}") ` +
123
- `FROM "${fk.targetTable}" ${T} ` +
124
- `WHERE ${T}."${fk.fkCol}" = "${baseTable()}"."${fk.parentCol}") AS "${fieldCol}"`,
125
- );
126
- } else if (origin instanceof MetaPassthroughOrigin || origin instanceof MetaCollectionOrigin) {
127
- blocked = `field "${f.name}" uses an origin shape not yet supported by TS migrate-ts (passthrough-via / collection — deferred)`;
128
- break;
129
- } else {
130
- blocked = `field "${f.name}" has no resolvable origin`;
131
- break;
132
- }
133
- }
134
-
135
- if (blocked !== undefined || baseEntity === undefined || cols.length === 0) {
136
- // Caller decides what to do with a null result. For v1 the runner just
137
- // omits it from expected.views — diff sees no expected view, no
138
- // create-view change, and an actual leftover view (if any) gets dropped.
139
- return null;
140
- }
141
-
142
- const sql = `SELECT\n${cols.join(",\n")}\nFROM "${baseTable()}"`;
143
- const view: ViewDescriptor = { name: viewName, sql };
144
- return view;
145
- }
146
-
147
- function splitDot(s: string): [string, string] {
148
- const i = s.indexOf(".");
149
- return [s.slice(0, i), s.slice(i + 1)];
150
- }
151
-
152
- function resolveColumnByName(
153
- owner: MetaObject | undefined, fieldName: string, strategy: ColumnNamingStrategy,
154
- ): string {
155
- if (owner === undefined) return fieldName;
156
- const field = owner.fields().find((f) => f.name === fieldName);
157
- return field ? resolveColumnName(field, strategy) : fieldName;
158
- }
159
-
160
- interface ToManyFk {
161
- target: MetaObject;
162
- targetTable: string;
163
- /** Column on the target entity holding the FK back to the base. */
164
- fkCol: string;
165
- /** Column on the base entity the FK references (usually the base's PK). */
166
- parentCol: string;
167
- }
168
-
169
- function resolveToManyFk(
170
- root: MetaRoot, baseEntityName: string, relName: string, strategy: ColumnNamingStrategy,
171
- ): ToManyFk | null {
172
- const baseObj = root.findObject(baseEntityName);
173
- if (baseObj === undefined) return null;
174
- const rel = baseObj.relationships().find((r) => r.name === relName);
175
- if (rel === undefined || rel.objectRef === undefined) return null;
176
- const target = root.findObject(stripPackage(rel.objectRef));
177
- if (target === undefined) return null;
178
-
179
- const fkRef = target.referenceIdentities().find(
180
- (r) => r.targetEntity !== undefined && stripPackage(r.targetEntity) === baseEntityName,
181
- );
182
- if (fkRef === undefined) return null;
183
- const fkFields = fkRef.fields;
184
- if (fkFields.length === 0) return null;
185
- const fkCol = resolveColumnByName(target, fkFields[0]!, strategy);
186
-
187
- const parentFieldName = fkRef.targetFields.length > 0
188
- ? fkRef.targetFields[0]!
189
- : baseObj.primaryIdentity()?.fields[0];
190
- if (parentFieldName === undefined) return null;
191
- const parentCol = resolveColumnByName(baseObj, parentFieldName, strategy);
192
-
193
- return { target, targetTable: resolveTableName(target), fkCol, parentCol };
194
- }
195
-
@@ -1,49 +0,0 @@
1
- import { classifyViewDiff, type ViewShape } from "./view-diff.js";
2
- import { emitPostgresViewMigration } from "./view-ddl-postgres.js";
3
- import { emitSqliteViewMigration } from "./view-ddl-sqlite.js";
4
-
5
- export interface ViewMigrationInput {
6
- readonly viewName: string;
7
- /** Previous view shape (from migration log / prior generation). undefined = new view. */
8
- readonly prevShape?: ViewShape;
9
- readonly nextShape: ViewShape;
10
- /** Full `CREATE VIEW ... AS ...;` SQL for nextShape. */
11
- readonly createSql: string;
12
- }
13
-
14
- export interface ViewMigrationsOpts {
15
- readonly dialect: "postgres" | "sqlite";
16
- readonly allowBreaking: boolean;
17
- readonly views: readonly ViewMigrationInput[];
18
- }
19
-
20
- export interface ViewMigrationsResult {
21
- readonly migrations: readonly string[];
22
- readonly errors: readonly string[];
23
- }
24
-
25
- export function computeViewMigrations(opts: ViewMigrationsOpts): ViewMigrationsResult {
26
- const migrations: string[] = [];
27
- const errors: string[] = [];
28
-
29
- for (const view of opts.views) {
30
- const diffClass = view.prevShape
31
- ? classifyViewDiff(view.prevShape, view.nextShape)
32
- : "safe-append"; // new view = treated like safe-append
33
-
34
- if (diffClass === "breaking" && !opts.allowBreaking) {
35
- errors.push(
36
- `View ${view.viewName} has a breaking change. Pass --allow-breaking to allow drop+recreate.`
37
- );
38
- continue;
39
- }
40
-
41
- const emit = opts.dialect === "postgres"
42
- ? emitPostgresViewMigration
43
- : emitSqliteViewMigration;
44
- const sql = emit({ diffClass, viewName: view.viewName, createSql: view.createSql });
45
- if (sql) migrations.push(sql);
46
- }
47
-
48
- return { migrations, errors };
49
- }
@@ -1,15 +0,0 @@
1
- import type { ViewMigrationOpts } from "./view-diff.js";
2
-
3
- export { type ViewMigrationOpts } from "./view-diff.js";
4
-
5
- export function emitPostgresViewMigration(opts: ViewMigrationOpts): string {
6
- switch (opts.diffClass) {
7
- case "no-change": return "";
8
- case "safe-append":
9
- case "safe-replace":
10
- // Replace CREATE VIEW with CREATE OR REPLACE VIEW.
11
- return opts.createSql.replace(/^CREATE VIEW\b/i, "CREATE OR REPLACE VIEW");
12
- case "breaking":
13
- return `DROP VIEW IF EXISTS ${opts.viewName} CASCADE;\n${opts.createSql}`;
14
- }
15
- }
@@ -1,7 +0,0 @@
1
- import type { ViewMigrationOpts } from "./view-diff.js";
2
-
3
- export function emitSqliteViewMigration(opts: ViewMigrationOpts): string {
4
- if (opts.diffClass === "no-change") return "";
5
- // SQLite has no CREATE OR REPLACE VIEW; must drop+create. Wrap in txn.
6
- return `BEGIN;\nDROP VIEW IF EXISTS ${opts.viewName};\n${opts.createSql}\nCOMMIT;`;
7
- }