@substrat-run/model-emit 0.1.0 → 0.3.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 +2 -2
- package/dist/emit-sql.d.ts +31 -0
- package/dist/emit-sql.d.ts.map +1 -1
- package/dist/emit-sql.js +111 -30
- package/dist/emit-sql.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts +13 -1
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +69 -3
- package/dist/journal.js.map +1 -1
- package/dist/plan.d.ts +35 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +179 -0
- package/dist/plan.js.map +1 -0
- package/package.json +7 -4
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()` |
|
|
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
|
|
package/dist/emit-sql.d.ts
CHANGED
|
@@ -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
|
package/dist/emit-sql.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
|
141
|
+
for (const name of tableOrder(entities)) {
|
|
93
142
|
const entity = entities[name];
|
|
94
143
|
if (!entity)
|
|
95
144
|
continue;
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
package/dist/emit-sql.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit-sql.js","sourceRoot":"","sources":["../src/emit-sql.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAkB,MAAM,yBAAyB,CAAC;AAWtE,8EAA8E;AAC9E,SAAS,IAAI,CAAC,MAAoB;IAChC,IAAI,CAAC,GAAG,MAAM,CAAC;IACf,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,sEAAsE;IACtE,SAAS,CAAC;QACR,MAAM,GAAG,GAAI,CAAgE,CAAC,IAAI,CAAC;QACnF,MAAM,CAAC,GAAG,GAAG,EAAE,QAAQ,IAAK,CAAuB,CAAC,IAAI,CAAC;QACzD,IAAI,CAAC,CAAC,KAAK,aAAa,IAAI,CAAC,KAAK,aAAa,IAAI,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,UAAU,CAAC,IAAI,GAAG,EAAE,SAAS,EAAE,CAAC;YAC3G,QAAQ,GAAG,IAAI,CAAC;YAChB,CAAC,GAAG,GAAG,CAAC,SAAS,CAAC;YAClB,SAAS;QACX,CAAC;QACD,2DAA2D;QAC3D,MAAM,MAAM,GAAI,CAAqC,CAAC,MAAM,CAAC;QAC7D,IAAI,OAAO,MAAM,KAAK,UAAU,IAAI,CAAC,CAAC,KAAK,aAAa,IAAI,CAAC,KAAK,aAAa,CAAC,EAAE,CAAC;YACjF,QAAQ,GAAG,IAAI,CAAC;YAChB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;IAChC,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,MAAoB,EAAE,KAAa;IAClE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,IAAI,GAAI,KAA2B,CAAC,IAAI,IAAK,KAA0C,CAAC,IAAI,EAAE,QAAQ,CAAC;IAE7G,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACvF,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,YAAY;
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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,
|
|
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
|
package/dist/journal.d.ts.map
CHANGED
|
@@ -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,
|
|
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(
|
|
48
|
-
|
|
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
|
package/dist/journal.js.map
CHANGED
|
@@ -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,
|
|
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
|
package/dist/plan.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEzE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAwB9D,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,QAAW,EACX,OAAgB;IAEhB,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,cAAc,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,MAAM,cAAc,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAElD,yEAAyE;IACzE,4DAA4D;IAC5D,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+C,CAAC;IACrE,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAEjG,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,8EAA8E;IAC9E,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,SAAS;QACjC,QAAQ,CAAC,IAAI,CACX,UAAU,KAAK,uEAAuE;YACpF,4EAA4E,CAC/E,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEhC,4EAA4E;QAC5E,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC3B,IAAI,CAAC,CAAC;gBAAE,SAAS;YACjB,sEAAsE;YACtE,yEAAyE;YACzE,kEAAkE;YAClE,MAAM,IAAI,GAAG;gBACX,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC;gBACjE,GAAG,iBAAiB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;aAC5D,CAAC;YACF,UAAU,CAAC,IAAI,CAAC,gBAAgB,KAAK,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACpE,OAAO,CAAC,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;YAC7B,SAAS;QACX,CAAC;QAED,4EAA4E;QAC5E,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAEtD,0EAA0E;QAC1E,wEAAwE;QACxE,0EAA0E;QAC1E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC1C,KAAK,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,CAAC;YAC7E,IAAI,OAAO,QAAQ,KAAK,QAAQ;gBAAE,SAAS;YAC3C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,CAAC;gBAC7C,QAAQ,CAAC,IAAI,CACX,IAAI,KAAK,IAAI,OAAO,kCAAkC,QAAQ,uBAAuB;oBACnF,8DAA8D,CACjE,CAAC;gBACF,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC/E,CAAC;QAED,KAAK,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,OAAO,EAAE,CAAC;YAC1C,UAAU,CAAC,IAAI,CAAC,eAAe,KAAK,kBAAkB,QAAQ,OAAO,OAAO,GAAG,CAAC,CAAC;YACjF,OAAO,CAAC,IAAI,CAAC,UAAU,KAAK,IAAI,QAAQ,OAAO,OAAO,EAAE,CAAC,CAAC;QAC5D,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YACjC,wEAAwE;YACxE,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YACpC,IAAI,GAAG,CAAC,sBAAsB,EAAE,CAAC;gBAC/B,QAAQ,CAAC,IAAI,CACX,IAAI,KAAK,IAAI,GAAG,CAAC,IAAI,0CAA0C,KAAK,qBAAqB;oBACvF,gFAAgF;oBAChF,qEAAqE,CACxE,CAAC;gBACF,SAAS;YACX,CAAC;YACD,UAAU,CAAC,IAAI,CAAC,eAAe,KAAK,eAAe,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC,OAAO,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;QAED,4EAA4E;QAC5E,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9C,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;YACxB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;gBAAE,SAAS;YACnD,wDAAwD;YACxD,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YACpC,QAAQ,CAAC,IAAI,CACX,IAAI,KAAK,IAAI,IAAI,wEAAwE;gBACvF,kFAAkF;gBAClF,gCAAgC,IAAI,wDAAwD,CAC/F,CAAC;QACJ,CAAC;QAED,4EAA4E;QAC5E,qEAAqE;QACrE,yEAAyE;QACzE,2EAA2E;QAC3E,yEAAyE;QACzE,2BAA2B;QAC3B,MAAM,WAAW,GAAG,iBAAiB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAChE,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACzC,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,IAAI,CAAC,CACd,CAAC;QACF,wEAAwE;QACxE,4EAA4E;QAC5E,2EAA2E;QAC3E,4DAA4D;QAC5D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC1F,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,GAAG,EAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC9D,CAAC;aACE,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;aACvC,IAAI,CAAC,IAAI,CAAC,CACd,CACF,CAAC;QACF,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;YAC/B,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YACpC,QAAQ,CAAC,IAAI,CACX,IAAI,KAAK,0BAA0B,IAAI,4CAA4C;gBACjF,qFAAqF;gBACrF,6CAA6C,CAChD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IACvE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAE3D,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAChD,4EAA4E;IAC5E,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAE,OAAO,CAAC,CAAC,CAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,OAAO,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC;IAC5G,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;AACpF,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;QACrB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,yCAAyC,CAAC;QAC/E,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACtB,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KACjC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEhE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC9B,IAAI,CAAC,CAAC,OAAO,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACb,kBAAkB,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,OAAO,kCAAkC;gBACjF,wEAAwE,CAC3E,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@substrat-run/model-emit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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": {
|
|
@@ -22,16 +22,19 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"
|
|
26
|
-
"@substrat-run/contracts": "0.71.0"
|
|
25
|
+
"@substrat-run/contracts": "0.73.0"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|
|
29
28
|
"typescript": "^7.0.0",
|
|
30
|
-
"vitest": "^3.
|
|
29
|
+
"vitest": "^3.2.7",
|
|
30
|
+
"zod": "^4.4.3"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"zod": "^4.4.0"
|
|
37
|
+
},
|
|
35
38
|
"scripts": {
|
|
36
39
|
"build": "tsc -p tsconfig.json",
|
|
37
40
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|