@substrat-run/model-emit 0.1.0 → 0.2.0

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.
package/README.md CHANGED
@@ -52,9 +52,9 @@ const sql = emitTables(entities);
52
52
  | `id` | `id TEXT PRIMARY KEY NOT NULL` |
53
53
  | `z.string()` / `.nullable()` | `TEXT NOT NULL` / `TEXT` |
54
54
  | `z.number()` | `INTEGER` |
55
- | `z.boolean()` | `INTEGER` — SQLite has no boolean |
55
+ | `z.boolean()` | **refused** — SQLite returns 0/1, so declare `z.number()` and keep the row type honest (`z.boolean()` stays right for an operation's *input*) |
56
56
  | `z.enum(['a','b'])` | `TEXT NOT NULL CHECK (col IN ('a','b'))` |
57
- | `key: ['number']` | `UNIQUE (number)` |
57
+ | `key: ['number']` | `UNIQUE (number)` — composite over all its fields: `key: ['a','b']` is one `UNIQUE (a, b)` |
58
58
  | `parents: ['customer']` | `REFERENCES acme_customers(id)` on the matching `customer_id` |
59
59
  | `jsonColumn('because…')` | `TEXT` |
60
60
 
@@ -15,4 +15,35 @@ export interface EmitSqlOptions {
15
15
  * entity declares such a column — never invented if it does not.
16
16
  */
17
17
  export declare function emitTables<T extends Record<string, EntityDef>>(entities: T, options?: EmitSqlOptions): string;
18
+ /** One emitted column: its name, its full definition, and whether SQLite could ADD it. */
19
+ export interface EmittedColumn {
20
+ readonly name: string;
21
+ /** `owner_id TEXT NOT NULL REFERENCES todo_owners(id)` */
22
+ readonly ddl: string;
23
+ /** NOT NULL with no default cannot be added to a table that already has rows. */
24
+ readonly requiredWithoutDefault: boolean;
25
+ }
26
+ /**
27
+ * The columns one entity emits, shared by `emitTables` and the migration
28
+ * planner.
29
+ *
30
+ * Extracted rather than duplicated: the planner has to render a column the exact
31
+ * way the table would have rendered it, or an `ALTER TABLE ADD COLUMN` produces
32
+ * a schema subtly unlike the one a fresh `CREATE TABLE` would.
33
+ */
34
+ export declare function columnsOf<T extends Record<string, EntityDef>>(name: string, entity: EntityDef, entities: T): EmittedColumn[];
35
+ /**
36
+ * The natural key, as ONE constraint over all its fields.
37
+ *
38
+ * `key: ['list_id', 'principal']` means "one share per person per list" and
39
+ * emits `UNIQUE (list_id, principal)`. It used to emit one UNIQUE per field —
40
+ * "a list may be shared once, ever" AND "a person may receive one share, ever",
41
+ * two wrong constraints silently replacing the composite. Stricter than
42
+ * intended, so it failed closed rather than open, and nothing said so.
43
+ *
44
+ * Every declaration in the fleet was single-field when this changed, so the two
45
+ * readings agreed everywhere and nothing could reveal the difference until an
46
+ * app needed a composite.
47
+ */
48
+ export declare function uniqueConstraints(name: string, entity: EntityDef): string[];
18
49
  //# sourceMappingURL=emit-sql.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"emit-sql.d.ts","sourceRoot":"","sources":["../src/emit-sql.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAwEtE,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAC5D,QAAQ,EAAE,CAAC,EACX,OAAO,GAAE,cAAmB,GAC3B,MAAM,CAsCR"}
1
+ {"version":3,"file":"emit-sql.d.ts","sourceRoot":"","sources":["../src/emit-sql.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AA+HtE,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAC5D,QAAQ,EAAE,CAAC,EACX,OAAO,GAAE,cAAmB,GAC3B,MAAM,CAeR;AAED,0FAA0F;AAC1F,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;CAC1C;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAC3D,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,CAAC,GACV,aAAa,EAAE,CAuBjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,CAO3E"}
package/dist/emit-sql.js CHANGED
@@ -48,8 +48,19 @@ function columnFor(name, schema, where) {
48
48
  const kind = inner.type ?? inner._def?.typeName;
49
49
  if (kind === 'string' || kind === 'ZodString')
50
50
  return { name, type: 'TEXT', nullable };
51
- if (kind === 'boolean' || kind === 'ZodBoolean')
52
- return { name, type: 'INTEGER', nullable };
51
+ if (kind === 'boolean' || kind === 'ZodBoolean') {
52
+ // INTEGER is the right COLUMN, and `boolean` is the wrong TYPE to promise:
53
+ // SQLite hands back 0/1, so `EntityRow` would infer a boolean the database
54
+ // can never return. Refused rather than emitted, per rule 1 — a silent
55
+ // default here is a type error that typechecks.
56
+ //
57
+ // Note the asymmetry, which is why the message says it: `z.boolean()` is
58
+ // CORRECT for an operation's input, which crosses JSON. An app can take
59
+ // `done: z.boolean()` and store `done: z.number()`, and both are right.
60
+ throw new Error(`emit-sql: ${where} is z.boolean(), which stores as INTEGER — declare it z.number() so ` +
61
+ 'the row type matches what SQLite returns. (z.boolean() stays correct for an ' +
62
+ "operation's input, which crosses JSON.)");
63
+ }
53
64
  if (kind === 'number' || kind === 'ZodNumber') {
54
65
  // Ints and reals both land in NUMERIC affinity; INTEGER is the honest
55
66
  // default for counts, and money is a string by platform rule (K-14).
@@ -75,6 +86,44 @@ function columnFor(name, schema, where) {
75
86
  throw new Error(`emit-sql: cannot map ${where} (zod kind '${String(kind)}') to a column — ` +
76
87
  `map it explicitly, or model the field as one this understands`);
77
88
  }
89
+ /**
90
+ * Parents before children, alphabetical within a tier.
91
+ *
92
+ * Sorting by name alone emitted `todo_items` — which REFERENCES `todo_lists` —
93
+ * first. SQLite tolerates a forward reference; a stricter engine does not, and
94
+ * "it happened to work" is not a property to ship. Deterministic either way,
95
+ * which is what lets the output be diffed.
96
+ *
97
+ * A cycle cannot be ordered, so it is reported rather than silently truncated:
98
+ * remaining entities are appended in name order after the error names them.
99
+ */
100
+ function tableOrder(entities) {
101
+ const names = Object.keys(entities).sort();
102
+ const placed = new Set();
103
+ const out = [];
104
+ let progress = true;
105
+ while (progress && out.length < names.length) {
106
+ progress = false;
107
+ for (const name of names) {
108
+ if (placed.has(name))
109
+ continue;
110
+ const parents = entities[name]?.parents ?? [];
111
+ // A parent outside this registry (a composed engine's entity) cannot be
112
+ // emitted here and therefore cannot be waited for.
113
+ const blocked = parents.some((p) => p in entities && !placed.has(String(p)));
114
+ if (blocked)
115
+ continue;
116
+ placed.add(name);
117
+ out.push(name);
118
+ progress = true;
119
+ }
120
+ }
121
+ if (out.length < names.length) {
122
+ const cyclic = names.filter((n) => !placed.has(n));
123
+ throw new Error(`emit-sql: parent cycle among ${cyclic.join(', ')} — a table cannot be created before itself`);
124
+ }
125
+ return out;
126
+ }
78
127
  /**
79
128
  * `CREATE TABLE` for every declared entity, in sorted order.
80
129
  *
@@ -89,39 +138,71 @@ function columnFor(name, schema, where) {
89
138
  export function emitTables(entities, options = {}) {
90
139
  const exists = options.ifNotExists ? 'IF NOT EXISTS ' : '';
91
140
  const out = [];
92
- for (const name of Object.keys(entities).sort()) {
141
+ for (const name of tableOrder(entities)) {
93
142
  const entity = entities[name];
94
143
  if (!entity)
95
144
  continue;
96
- const shape = entity.fields.shape;
97
- const cols = [];
98
- for (const [field, schema] of Object.entries(shape)) {
99
- const c = columnFor(field, schema, `${name}.${field}`);
100
- if (field === 'id') {
101
- cols.push(` id ${c.type} PRIMARY KEY NOT NULL`);
102
- continue;
103
- }
104
- let line = ` ${c.name} ${c.type}`;
105
- if (!c.nullable)
106
- line += ' NOT NULL';
107
- if (c.check)
108
- line += ` ${c.check}`;
109
- // A parent edge whose id column is present becomes a real foreign key.
110
- for (const parent of entity.parents ?? []) {
111
- const parentTable = entities[parent]?.table;
112
- if (parentTable && c.name === `${String(parent).replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase()}_id`) {
113
- line += ` REFERENCES ${parentTable}(id)`;
114
- }
115
- }
116
- cols.push(line);
117
- }
118
- for (const k of entity.key ?? []) {
119
- if (!(k in shape))
120
- throw new Error(`emit-sql: ${name}.key names '${k}', which is not a field`);
121
- cols.push(` UNIQUE (${k})`);
122
- }
145
+ const cols = [
146
+ ...columnsOf(name, entity, entities).map((c) => ` ${c.ddl}`),
147
+ ...uniqueConstraints(name, entity).map((u) => ` ${u}`),
148
+ ];
123
149
  out.push(`CREATE TABLE ${exists}${entity.table} (\n${cols.join(',\n')}\n);`);
124
150
  }
125
151
  return out.join('\n\n');
126
152
  }
153
+ /**
154
+ * The columns one entity emits, shared by `emitTables` and the migration
155
+ * planner.
156
+ *
157
+ * Extracted rather than duplicated: the planner has to render a column the exact
158
+ * way the table would have rendered it, or an `ALTER TABLE ADD COLUMN` produces
159
+ * a schema subtly unlike the one a fresh `CREATE TABLE` would.
160
+ */
161
+ export function columnsOf(name, entity, entities) {
162
+ const shape = entity.fields.shape;
163
+ const out = [];
164
+ for (const [field, schema] of Object.entries(shape)) {
165
+ const c = columnFor(field, schema, `${name}.${field}`);
166
+ if (field === 'id') {
167
+ out.push({ name: 'id', ddl: `id ${c.type} PRIMARY KEY NOT NULL`, requiredWithoutDefault: true });
168
+ continue;
169
+ }
170
+ let ddl = `${c.name} ${c.type}`;
171
+ if (!c.nullable)
172
+ ddl += ' NOT NULL';
173
+ if (c.check)
174
+ ddl += ` ${c.check}`;
175
+ // A parent edge whose id column is present becomes a real foreign key.
176
+ for (const parent of entity.parents ?? []) {
177
+ const parentTable = entities[parent]?.table;
178
+ if (parentTable && c.name === `${String(parent).replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase()}_id`) {
179
+ ddl += ` REFERENCES ${parentTable}(id)`;
180
+ }
181
+ }
182
+ out.push({ name: c.name, ddl, requiredWithoutDefault: !c.nullable });
183
+ }
184
+ return out;
185
+ }
186
+ /**
187
+ * The natural key, as ONE constraint over all its fields.
188
+ *
189
+ * `key: ['list_id', 'principal']` means "one share per person per list" and
190
+ * emits `UNIQUE (list_id, principal)`. It used to emit one UNIQUE per field —
191
+ * "a list may be shared once, ever" AND "a person may receive one share, ever",
192
+ * two wrong constraints silently replacing the composite. Stricter than
193
+ * intended, so it failed closed rather than open, and nothing said so.
194
+ *
195
+ * Every declaration in the fleet was single-field when this changed, so the two
196
+ * readings agreed everywhere and nothing could reveal the difference until an
197
+ * app needed a composite.
198
+ */
199
+ export function uniqueConstraints(name, entity) {
200
+ const shape = entity.fields.shape;
201
+ const key = entity.key ?? [];
202
+ for (const k of key) {
203
+ if (!(k in shape))
204
+ throw new Error(`emit-sql: ${name}.key names '${k}', which is not a field`);
205
+ }
206
+ return key.length > 0 ? [`UNIQUE (${key.join(', ')})`] : [];
207
+ }
127
208
  //# sourceMappingURL=emit-sql.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"emit-sql.js","sourceRoot":"","sources":["../src/emit-sql.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"emit-sql.js","sourceRoot":"","sources":["../src/emit-sql.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- export { emitTables, type EmitSqlOptions } from './emit-sql.js';
2
- export { journalColumns } from './journal.js';
1
+ export { emitTables, columnsOf, uniqueConstraints, type EmitSqlOptions, type EmittedColumn } from './emit-sql.js';
2
+ export { journalColumns, journalUniques } from './journal.js';
3
+ export { planMigration, parseJournal, type Journal, type JournalEntry, type MigrationPlan, } from './plan.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,iBAAiB,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAClH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EACL,aAAa,EACb,YAAY,EACZ,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
- export { emitTables } from './emit-sql.js';
2
- export { journalColumns } from './journal.js';
1
+ export { emitTables, columnsOf, uniqueConstraints } from './emit-sql.js';
2
+ export { journalColumns, journalUniques } from './journal.js';
3
+ export { planMigration, parseJournal, } from './plan.js';
3
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAuB,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,iBAAiB,EAA2C,MAAM,eAAe,CAAC;AAClH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EACL,aAAa,EACb,YAAY,GAIb,MAAM,WAAW,CAAC"}
package/dist/journal.d.ts CHANGED
@@ -18,8 +18,20 @@
18
18
  *
19
19
  * Handles what real journals do: multi-line `CHECK (...)` constraints (tracked
20
20
  * by paren depth, so a continuation line is not read as a column), `ADD COLUMN`,
21
- * `DROP TABLE`, and `RENAME TO` — append-only journals rebuild a table by
21
+ * `DROP TABLE`, `RENAME COLUMN` and `RENAME TO` — append-only journals rebuild a table by
22
22
  * creating a `_new`, copying, dropping the original and renaming onto its name.
23
23
  */
24
24
  export declare function journalColumns(sql: string): Map<string, Set<string>>;
25
+ /**
26
+ * UNIQUE constraints per table, read out of a journal.
27
+ *
28
+ * `journalColumns` deliberately skips constraint lines — it answers "which
29
+ * columns exist". But a declared `key` is a schema fact too, and adding one to
30
+ * an entity that already has a table is a change SQLite cannot make in place.
31
+ * Without this the planner reported "up to date" over a missing constraint.
32
+ *
33
+ * Normalised to `a, b` (single space, declaration order preserved) so the same
34
+ * constraint written two ways compares equal.
35
+ */
36
+ export declare function journalUniques(sql: string): Map<string, Set<string>>;
25
37
  //# sourceMappingURL=journal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAsCpE"}
1
+ {"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CA4CpE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAwDpE"}
package/dist/journal.js CHANGED
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * Handles what real journals do: multi-line `CHECK (...)` constraints (tracked
20
20
  * by paren depth, so a continuation line is not read as a column), `ADD COLUMN`,
21
- * `DROP TABLE`, and `RENAME TO` — append-only journals rebuild a table by
21
+ * `DROP TABLE`, `RENAME COLUMN` and `RENAME TO` — append-only journals rebuild a table by
22
22
  * creating a `_new`, copying, dropping the original and renaming onto its name.
23
23
  */
24
24
  export function journalColumns(sql) {
@@ -44,12 +44,21 @@ export function journalColumns(sql) {
44
44
  }
45
45
  // Replayed in statement order: a journal may add a column and later rename the
46
46
  // table, or rename onto a name it has just dropped.
47
- for (const m of sql.matchAll(/(?:ALTER TABLE ([a-z_][a-z0-9_]*)\s+ADD COLUMN\s+([a-z_][a-z0-9_]*))|(?:ALTER TABLE ([a-z_][a-z0-9_]*)\s+RENAME TO\s+([a-z_][a-z0-9_]*))|(?:DROP TABLE (?:IF EXISTS )?([a-z_][a-z0-9_]*))/gi)) {
48
- const [, addTable, addCol, fromTable, toTable, dropped] = m;
47
+ for (const m of sql.matchAll(
48
+ // `RENAME COLUMN` comes first: `RENAME TO` must not swallow it. Without this
49
+ // branch a renamed column reads as its old name forever, and a planner that
50
+ // derives migrations would emit the same rename on every run.
51
+ /(?:ALTER TABLE ([a-z_][a-z0-9_]*)\s+ADD COLUMN\s+([a-z_][a-z0-9_]*))|(?:ALTER TABLE ([a-z_][a-z0-9_]*)\s+RENAME COLUMN\s+([a-z_][a-z0-9_]*)\s+TO\s+([a-z_][a-z0-9_]*))|(?:ALTER TABLE ([a-z_][a-z0-9_]*)\s+RENAME TO\s+([a-z_][a-z0-9_]*))|(?:DROP TABLE (?:IF EXISTS )?([a-z_][a-z0-9_]*))/gi)) {
52
+ const [, addTable, addCol, renTable, renFrom, renTo, fromTable, toTable, dropped] = m;
49
53
  if (addTable && addCol)
50
54
  tables.get(addTable)?.add(addCol);
51
55
  else if (dropped)
52
56
  tables.delete(dropped);
57
+ else if (renTable && renFrom && renTo) {
58
+ const cols = tables.get(renTable);
59
+ if (cols?.delete(renFrom))
60
+ cols.add(renTo);
61
+ }
53
62
  else if (fromTable && toTable) {
54
63
  const cols = tables.get(fromTable);
55
64
  if (cols) {
@@ -60,4 +69,61 @@ export function journalColumns(sql) {
60
69
  }
61
70
  return tables;
62
71
  }
72
+ /**
73
+ * UNIQUE constraints per table, read out of a journal.
74
+ *
75
+ * `journalColumns` deliberately skips constraint lines — it answers "which
76
+ * columns exist". But a declared `key` is a schema fact too, and adding one to
77
+ * an entity that already has a table is a change SQLite cannot make in place.
78
+ * Without this the planner reported "up to date" over a missing constraint.
79
+ *
80
+ * Normalised to `a, b` (single space, declaration order preserved) so the same
81
+ * constraint written two ways compares equal.
82
+ */
83
+ export function journalUniques(sql) {
84
+ const tables = new Map();
85
+ for (const [, table, body] of sql.matchAll(/CREATE TABLE (?:IF NOT EXISTS )?([a-z_][a-z0-9_]*)\s*\(([\s\S]*?)\n\s*\);/gi)) {
86
+ if (!table || !body)
87
+ continue;
88
+ const found = new Set();
89
+ for (const [, cols] of body.matchAll(/\bUNIQUE\s*\(([^)]*)\)/gi)) {
90
+ if (!cols)
91
+ continue;
92
+ found.add(cols
93
+ .split(',')
94
+ .map((c) => c.trim())
95
+ .filter(Boolean)
96
+ .join(', '));
97
+ }
98
+ tables.set(table, found);
99
+ }
100
+ // Replayed in statement order, like `journalColumns`.
101
+ for (const m of sql.matchAll(/(?:ALTER TABLE ([a-z_][a-z0-9_]*)\s+RENAME COLUMN\s+([a-z_][a-z0-9_]*)\s+TO\s+([a-z_][a-z0-9_]*))|(?:ALTER TABLE ([a-z_][a-z0-9_]*)\s+RENAME TO\s+([a-z_][a-z0-9_]*))|(?:DROP TABLE (?:IF EXISTS )?([a-z_][a-z0-9_]*))/gi)) {
102
+ const [, renTable, renFrom, renTo, fromTable, toTable, dropped] = m;
103
+ if (dropped) {
104
+ tables.delete(dropped);
105
+ }
106
+ else if (renTable && renFrom && renTo) {
107
+ // SQLite rewrites the constraint when a column is renamed — verified
108
+ // against a real database — so the reader has to as well. Missing this
109
+ // makes a renamed key look like a key the journal never had.
110
+ const cs = tables.get(renTable);
111
+ if (cs) {
112
+ tables.set(renTable, new Set([...cs].map((c) => c
113
+ .split(', ')
114
+ .map((col) => (col === renFrom ? renTo : col))
115
+ .join(', '))));
116
+ }
117
+ }
118
+ else if (fromTable && toTable) {
119
+ // A rebuild renames a table onto another's name; constraints travel with it.
120
+ const c = tables.get(fromTable);
121
+ if (c) {
122
+ tables.delete(fromTable);
123
+ tables.set(toTable, c);
124
+ }
125
+ }
126
+ }
127
+ return tables;
128
+ }
63
129
  //# sourceMappingURL=journal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE9C,KAAK,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CACxC,6EAA6E,CAC9E,EAAE,CAAC;QACF,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI;YAAE,SAAS;QAC9B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC;YAC1B,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;YAC7E,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,+CAA+C,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC3G,MAAM,IAAI,GAAG,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACtD,IAAI,IAAI;gBAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,+EAA+E;IAC/E,oDAAoD;IACpD,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,CAC1B,6LAA6L,CAC9L,EAAE,CAAC;QACF,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5D,IAAI,QAAQ,IAAI,MAAM;YAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;aACrD,IAAI,OAAO;YAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;aACpC,IAAI,SAAS,IAAI,OAAO,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACnC,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACzB,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"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"}
package/dist/plan.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ import type { EntityDef } from '@substrat-run/contracts';
2
+ export interface JournalEntry {
3
+ /** Derived, monotonic, zero-padded: `0001`. Never authored. */
4
+ readonly version: string;
5
+ /** Human label for the diff, derived from what changed. */
6
+ readonly slug: string;
7
+ readonly sql: string;
8
+ /**
9
+ * Shipped. A released entry is frozen — the planner appends after it and never
10
+ * touches it. Set by whatever ships the package, not by the generator.
11
+ */
12
+ readonly released?: boolean;
13
+ }
14
+ export interface Journal {
15
+ readonly entries: readonly JournalEntry[];
16
+ }
17
+ export type MigrationPlan = {
18
+ readonly kind: 'up-to-date';
19
+ } | {
20
+ readonly kind: 'append';
21
+ readonly entry: JournalEntry;
22
+ } | {
23
+ readonly kind: 'refused';
24
+ readonly reasons: readonly string[];
25
+ };
26
+ /**
27
+ * What one entry would have to say to bring the journal up to the model.
28
+ *
29
+ * Pure: same model + same journal → same plan, every time. It reads no clock and
30
+ * mints no id, which is what lets the result be committed and diffed.
31
+ */
32
+ export declare function planMigration<T extends Record<string, EntityDef>>(entities: T, journal: Journal): MigrationPlan;
33
+ /** Parsed hostilely: it is our file, and it is also somebody's merge resolution. */
34
+ export declare function parseJournal(raw: unknown): Journal;
35
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGzD,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;CAC3C;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;CAAE,GAC/B;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GACzD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAItE;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAC/D,QAAQ,EAAE,CAAC,EACX,OAAO,EAAE,OAAO,GACf,aAAa,CA6If;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAkBlD"}
package/dist/plan.js ADDED
@@ -0,0 +1,179 @@
1
+ /**
2
+ * The migration journal — derived, and the one artifact that stays a file.
3
+ *
4
+ * The manifest and the route table are pure functions of live objects, so they
5
+ * are computed when the module loads. A journal cannot be: it is append-only,
6
+ * frozen once shipped, and read by a human in a pull request.
7
+ *
8
+ * **Nobody writes the version number.** The model states the current shape; the
9
+ * journal states what has already been applied. Reconstruct the second, diff
10
+ * against the first, and if the diff is non-empty append exactly ONE entry with
11
+ * a derived counter. Declaring a version is declaring a fact a diff already
12
+ * knows — and hand-numbering has failed in practice: a production journal in a
13
+ * real app ships two entries numbered 0010, because two people numbered by hand
14
+ * in two branches.
15
+ *
16
+ * Two branches both generating `0003` collide in `journal.json`, which is the
17
+ * right signal on an ordered append-only list. Resolution is mechanical: merge
18
+ * the model, re-run, it renumbers.
19
+ *
20
+ * **What this refuses.** Anything that would rewrite history or lose data: a
21
+ * dropped table or column, a retyped column, or a required column added to a
22
+ * table that may already hold rows. Those are real decisions (expand/contract,
23
+ * a backfill, a `renamedFrom` declaration) and a generator that guessed at them
24
+ * would be guessing with somebody's data.
25
+ */
26
+ import { z } from 'zod';
27
+ import { columnsOf, emitTables, uniqueConstraints } from './emit-sql.js';
28
+ import { journalColumns, journalUniques } from './journal.js';
29
+ const pad = (n) => String(n).padStart(4, '0');
30
+ /**
31
+ * What one entry would have to say to bring the journal up to the model.
32
+ *
33
+ * Pure: same model + same journal → same plan, every time. It reads no clock and
34
+ * mints no id, which is what lets the result be committed and diffed.
35
+ */
36
+ export function planMigration(entities, journal) {
37
+ const journalSql = journal.entries.map((e) => e.sql).join('\n');
38
+ const applied = journalColumns(journalSql);
39
+ const desired = journalColumns(emitTables(entities));
40
+ const appliedUniques = journalUniques(journalSql);
41
+ // table name → the entity that owns it, so a diff can be reported in the
42
+ // vocabulary the model uses rather than in raw table names.
43
+ const owner = new Map();
44
+ for (const [name, entity] of Object.entries(entities))
45
+ owner.set(entity.table, { name, entity });
46
+ const statements = [];
47
+ const changes = [];
48
+ const refusals = [];
49
+ // -- gone from the model, still in the journal ------------------------------
50
+ for (const table of applied.keys()) {
51
+ if (desired.has(table))
52
+ continue;
53
+ refusals.push(`table '${table}' is in the journal but no longer in the model — dropping a table is ` +
54
+ 'expand/contract, not a diff: retire it deliberately, or restore the entity');
55
+ }
56
+ for (const [table, wanted] of desired) {
57
+ const have = applied.get(table);
58
+ // -- new table ------------------------------------------------------------
59
+ if (!have) {
60
+ const o = owner.get(table);
61
+ if (!o)
62
+ continue;
63
+ // Built here rather than by re-emitting a one-entity registry: parent
64
+ // edges resolve against the FULL model, and a subset would silently drop
65
+ // the REFERENCES clause of every foreign key pointing outside it.
66
+ const cols = [
67
+ ...columnsOf(o.name, o.entity, entities).map((c) => ` ${c.ddl}`),
68
+ ...uniqueConstraints(o.name, o.entity).map((u) => ` ${u}`),
69
+ ];
70
+ statements.push(`CREATE TABLE ${table} (\n${cols.join(',\n')}\n);`);
71
+ changes.push(`add-${table}`);
72
+ continue;
73
+ }
74
+ // -- new columns on an existing table -------------------------------------
75
+ const o = owner.get(table);
76
+ if (!o)
77
+ continue;
78
+ const emitted = columnsOf(o.name, o.entity, entities);
79
+ // `{ current: previous }`, kept only for names the journal still holds. A
80
+ // declaration whose old name has already gone is spent, not wrong — the
81
+ // rename shipped, and the entry is now a gravestone the model may delete.
82
+ const renames = new Map();
83
+ for (const [current, previous] of Object.entries(o.entity.renamedFrom ?? {})) {
84
+ if (typeof previous !== 'string')
85
+ continue;
86
+ if (!emitted.some((c) => c.name === current)) {
87
+ refusals.push(`'${table}.${current}' is declared as renamed from '${previous}', but no such field ` +
88
+ 'exists in the model — a rename names the field it renamed TO');
89
+ continue;
90
+ }
91
+ if (have.has(previous) && !have.has(current))
92
+ renames.set(current, previous);
93
+ }
94
+ for (const [current, previous] of renames) {
95
+ statements.push(`ALTER TABLE ${table} RENAME COLUMN ${previous} TO ${current};`);
96
+ changes.push(`rename-${table}-${previous}-to-${current}`);
97
+ }
98
+ for (const col of emitted) {
99
+ if (have.has(col.name))
100
+ continue;
101
+ // A renamed column is not a new one — emitting both would add it twice.
102
+ if (renames.has(col.name))
103
+ continue;
104
+ if (col.requiredWithoutDefault) {
105
+ refusals.push(`'${table}.${col.name}' is required and has no default, and '${table}' already exists — ` +
106
+ 'SQLite cannot add such a column to a table that may hold rows. Make the field ' +
107
+ 'nullable, give it a default, or backfill it in a hand-written entry');
108
+ continue;
109
+ }
110
+ statements.push(`ALTER TABLE ${table} ADD COLUMN ${col.ddl};`);
111
+ changes.push(`add-${table}-${col.name}`);
112
+ }
113
+ // -- columns the model dropped --------------------------------------------
114
+ const renamedAway = new Set(renames.values());
115
+ for (const name of have) {
116
+ if (emitted.some((c) => c.name === name))
117
+ continue;
118
+ // Accounted for: it did not go away, it got a new name.
119
+ if (renamedAway.has(name))
120
+ continue;
121
+ refusals.push(`'${table}.${name}' is in the journal but no longer in the model — a diff cannot tell a ` +
122
+ 'rename from a drop-plus-add, and guessing wrong loses the data. Declare it with ' +
123
+ `\`renamedFrom: { <newName>: '${name}' }\` on the entity, or retire the column deliberately`);
124
+ }
125
+ // -- a key declared after the table already exists ------------------------
126
+ // SQLite cannot ADD a UNIQUE constraint in place; it needs the table
127
+ // rebuilt, copied and renamed. That is a decision about live data, not a
128
+ // diff, so it is refused rather than guessed — and refused LOUDLY, because
129
+ // silently reporting "up to date" over a missing uniqueness guarantee is
130
+ // how a duplicate gets in.
131
+ const wantUniques = uniqueConstraints(o.name, o.entity).map((u) => (/UNIQUE\s*\(([^)]*)\)/i.exec(u)?.[1] ?? '')
132
+ .split(',')
133
+ .map((c) => c.trim())
134
+ .filter(Boolean)
135
+ .join(', '));
136
+ // Translated through any rename THIS plan is about to emit: the journal
137
+ // still names the old column, and SQLite rewrites the constraint along with
138
+ // it. Comparing untranslated makes a renamed key look like one the journal
139
+ // never had, and refuses the very change that would fix it.
140
+ const toCurrent = new Map([...renames].map(([current, previous]) => [previous, current]));
141
+ const haveUniques = new Set([...(appliedUniques.get(table) ?? new Set())].map((c) => c
142
+ .split(', ')
143
+ .map((col) => toCurrent.get(col) ?? col)
144
+ .join(', ')));
145
+ for (const want of wantUniques) {
146
+ if (haveUniques.has(want))
147
+ continue;
148
+ refusals.push(`'${table}' declares a key over (${want}) that the journal does not have — SQLite ` +
149
+ 'cannot add a UNIQUE constraint to an existing table without rebuilding it. Rebuild ' +
150
+ 'it in a hand-written entry, or drop the key');
151
+ }
152
+ }
153
+ if (refusals.length > 0)
154
+ return { kind: 'refused', reasons: refusals };
155
+ if (statements.length === 0)
156
+ return { kind: 'up-to-date' };
157
+ const version = pad(journal.entries.length + 1);
158
+ // One change names itself; several get a count, so the slug stays readable.
159
+ const slug = changes.length === 1 ? changes[0] : `${changes[0]}-and-${changes.length - 1}-more`;
160
+ return { kind: 'append', entry: { version, slug, sql: statements.join('\n\n') } };
161
+ }
162
+ /** Parsed hostilely: it is our file, and it is also somebody's merge resolution. */
163
+ export function parseJournal(raw) {
164
+ const entry = z.object({
165
+ version: z.string().regex(/^\d{4}$/, 'version is a derived four-digit counter'),
166
+ slug: z.string().min(1),
167
+ sql: z.string().min(1),
168
+ released: z.boolean().optional(),
169
+ });
170
+ const parsed = z.object({ entries: z.array(entry) }).parse(raw);
171
+ parsed.entries.forEach((e, i) => {
172
+ if (e.version !== pad(i + 1)) {
173
+ throw new Error(`journal: entry ${i + 1} is numbered '${e.version}' — the counter is derived from ` +
174
+ 'position, so a gap or a duplicate means a bad merge, not a renumbering');
175
+ }
176
+ });
177
+ return parsed;
178
+ }
179
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@substrat-run/model-emit",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Build-time tooling over a Substrat model — DDL emitted from the entity registry, and the journal reader that holds it honest",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "zod": "^4.0.0",
26
- "@substrat-run/contracts": "0.71.0"
26
+ "@substrat-run/contracts": "0.72.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "typescript": "^7.0.0",