@korajs/core 1.0.0-beta.12 → 1.0.0-beta.14

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/dist/internal.js CHANGED
@@ -1,9 +1,19 @@
1
1
  import {
2
+ SCHEMA_CEILING_DIRECTIVE,
2
3
  canonicalize,
4
+ collectionIndexName,
5
+ compareStamps,
3
6
  computeOperationId,
7
+ enumCheckConstraint,
8
+ legacyCollectionIndexName,
9
+ planField,
10
+ quoteIdent,
11
+ sqlDefaultLiteral,
12
+ sqlStringLiteral,
13
+ stampOf,
4
14
  topologicalSort,
5
15
  validateOperationParams
6
- } from "./chunk-5IICSH6H.js";
16
+ } from "./chunk-QUD3DMSD.js";
7
17
 
8
18
  // src/events/event-emitter.ts
9
19
  var SimpleEventEmitter = class {
@@ -45,10 +55,446 @@ var SimpleEventEmitter = class {
45
55
  return this.listeners.get(type)?.size ?? 0;
46
56
  }
47
57
  };
58
+
59
+ // src/schema/constraint-relaxation.ts
60
+ function isKoraInternalColumn(name) {
61
+ return name === "id" || name.startsWith("_");
62
+ }
63
+ function scanSqlTokens(sql) {
64
+ const tokens = [];
65
+ const pattern = /\s+|--[^\n]*|\/\*[\s\S]*?(?:\*\/|$)|'(?:[^']|'')*'?|"(?:[^"]|"")*"?|`(?:[^`]|``)*`?|\[[^\]]*\]?|[A-Za-z_][A-Za-z0-9_$]*|[0-9]+(?:\.[0-9]+)?|[(),]|[^\s(),'"`[A-Za-z0-9_]+/g;
66
+ for (let match = pattern.exec(sql); match !== null; match = pattern.exec(sql)) {
67
+ const text = match[0];
68
+ if (/^\s/.test(text) || text.startsWith("--") || text.startsWith("/*")) continue;
69
+ tokens.push({ text, start: match.index, end: match.index + text.length });
70
+ }
71
+ return tokens;
72
+ }
73
+ function unquoteSqliteName(token) {
74
+ if (token === void 0) return null;
75
+ if (/^"(?:[^"]|"")*"$/.test(token)) return token.slice(1, -1).replaceAll('""', '"');
76
+ if (/^`(?:[^`]|``)*`$/.test(token)) return token.slice(1, -1).replaceAll("``", "`");
77
+ if (/^\[[^\]]*\]$/.test(token)) return token.slice(1, -1);
78
+ if (/^'(?:[^']|'')*'$/.test(token)) return token.slice(1, -1).replaceAll("''", "'");
79
+ if (/^[A-Za-z_][A-Za-z0-9_$]*$/.test(token)) return token;
80
+ return null;
81
+ }
82
+ var TABLE_CONSTRAINT_KEYWORDS = /* @__PURE__ */ new Set([
83
+ "CONSTRAINT",
84
+ "PRIMARY",
85
+ "UNIQUE",
86
+ "CHECK",
87
+ "FOREIGN"
88
+ ]);
89
+ function parseSqliteCheckConstraints(createTableSql) {
90
+ const tokens = scanSqlTokens(createTableSql);
91
+ const open = tokens.findIndex((token) => token.text === "(");
92
+ if (open < 0) return [];
93
+ const definitions = [];
94
+ let current = [];
95
+ let depth = 0;
96
+ for (let i = open + 1; i < tokens.length; i++) {
97
+ const token = tokens[i];
98
+ if (token.text === "(") depth++;
99
+ if (token.text === ")") {
100
+ if (depth === 0) break;
101
+ depth--;
102
+ }
103
+ if (token.text === "," && depth === 0) {
104
+ definitions.push(current);
105
+ current = [];
106
+ continue;
107
+ }
108
+ current.push(token);
109
+ }
110
+ if (current.length > 0) definitions.push(current);
111
+ const checks = [];
112
+ for (const definition of definitions) {
113
+ const first = definition[0];
114
+ if (first === void 0) continue;
115
+ const column = TABLE_CONSTRAINT_KEYWORDS.has(first.text.toUpperCase()) ? null : unquoteSqliteName(first.text);
116
+ let level = 0;
117
+ for (let i = 0; i < definition.length; i++) {
118
+ const token = definition[i];
119
+ if (token.text === "(") level++;
120
+ else if (token.text === ")") level--;
121
+ if (level !== 0 || token.text.toUpperCase() !== "CHECK") continue;
122
+ const opening = definition[i + 1];
123
+ if (opening?.text !== "(") continue;
124
+ let inner = 0;
125
+ let closing;
126
+ for (let j = i + 1; j < definition.length; j++) {
127
+ const candidate = definition[j];
128
+ if (candidate.text === "(") inner++;
129
+ if (candidate.text === ")" && --inner === 0) {
130
+ closing = candidate;
131
+ break;
132
+ }
133
+ }
134
+ if (closing === void 0) continue;
135
+ const named = i >= 2 && definition[i - 2].text.toUpperCase() === "CONSTRAINT" ? unquoteSqliteName(definition[i - 1].text) : null;
136
+ checks.push({
137
+ name: named,
138
+ expression: createTableSql.slice(opening.end, closing.start).trim(),
139
+ column
140
+ });
141
+ }
142
+ }
143
+ return checks;
144
+ }
145
+ function isKoraSqliteEnumCheck(check, enumColumns) {
146
+ const shape = parseEnumCheckDefinition(`CHECK (${check.expression})`);
147
+ if (shape === null || isKoraInternalColumn(shape.column)) return false;
148
+ if (check.column !== null && check.column !== shape.column) return false;
149
+ return enumColumns === void 0 || enumColumns.includes(shape.column);
150
+ }
151
+ function sqliteTableNeedsRelaxation(catalog, enumColumns) {
152
+ if (parseSqliteCheckConstraints(catalog.sql).some(
153
+ (check) => isKoraSqliteEnumCheck(check, enumColumns)
154
+ )) {
155
+ return true;
156
+ }
157
+ return catalog.columns.some(
158
+ (column) => column.notnull === 1 && !isKoraInternalColumn(column.name)
159
+ );
160
+ }
161
+ function sqliteCheckReferencesColumn(check, columns) {
162
+ if (check.column !== null && columns.has(check.column)) return true;
163
+ return scanSqlTokens(check.expression).some((token) => {
164
+ if (token.text.startsWith("'")) return false;
165
+ const name = unquoteSqliteName(token.text);
166
+ return name !== null && columns.has(name);
167
+ });
168
+ }
169
+ function sqliteCheckConstraintDefinition(check) {
170
+ const name = check.name === null ? "" : `CONSTRAINT ${quoteIdent(check.name)} `;
171
+ return `${name}CHECK (${check.expression})`;
172
+ }
173
+ function sqliteConstraintRelaxationStatements(catalog, enumColumns) {
174
+ if (!sqliteTableNeedsRelaxation(catalog, enumColumns)) return [];
175
+ const table = catalog.table;
176
+ const temp = `_kora_relax_${table}`;
177
+ const ordered = [...catalog.columns].sort((a, b) => a.cid - b.cid);
178
+ const pkColumns = ordered.filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk);
179
+ const fkGroups = /* @__PURE__ */ new Map();
180
+ for (const fk of catalog.foreignKeys) {
181
+ const group = fkGroups.get(fk.id) ?? [];
182
+ group.push(fk);
183
+ fkGroups.set(fk.id, group);
184
+ }
185
+ const columnReferences = /* @__PURE__ */ new Map();
186
+ const tableConstraints = [];
187
+ for (const group of fkGroups.values()) {
188
+ group.sort((a, b) => a.seq - b.seq);
189
+ const first = group[0];
190
+ if (!first) continue;
191
+ const actions = foreignKeyActions(first);
192
+ const target = group.every((fk) => fk.to !== null && fk.to !== "") ? `${quoteIdent(first.table)}(${group.map((fk) => quoteIdent(fk.to ?? "")).join(", ")})` : quoteIdent(first.table);
193
+ if (group.length === 1) {
194
+ columnReferences.set(first.from, `REFERENCES ${target}${actions}`);
195
+ } else {
196
+ tableConstraints.push(
197
+ `FOREIGN KEY (${group.map((fk) => quoteIdent(fk.from)).join(", ")}) REFERENCES ${target}${actions}`
198
+ );
199
+ }
200
+ }
201
+ if (pkColumns.length > 1) {
202
+ tableConstraints.unshift(`PRIMARY KEY (${pkColumns.map((c) => quoteIdent(c.name)).join(", ")})`);
203
+ }
204
+ for (const unique of catalog.uniqueConstraints) {
205
+ tableConstraints.push(`UNIQUE (${unique.map(quoteIdent).join(", ")})`);
206
+ }
207
+ for (const check of parseSqliteCheckConstraints(catalog.sql)) {
208
+ if (!isKoraSqliteEnumCheck(check, enumColumns)) {
209
+ tableConstraints.push(sqliteCheckConstraintDefinition(check));
210
+ }
211
+ }
212
+ const definitions = ordered.map((column) => {
213
+ const parts = [quoteIdent(column.name)];
214
+ if (column.type) parts.push(column.type);
215
+ if (pkColumns.length === 1 && column.pk === 1) parts.push("PRIMARY KEY");
216
+ if (column.notnull === 1 && isKoraInternalColumn(column.name)) parts.push("NOT NULL");
217
+ if (column.dflt_value !== null) parts.push(`DEFAULT (${column.dflt_value})`);
218
+ const reference = columnReferences.get(column.name);
219
+ if (reference) parts.push(reference);
220
+ return parts.join(" ");
221
+ });
222
+ const columnList = ordered.map((column) => quoteIdent(column.name)).join(", ");
223
+ return [
224
+ `DROP TABLE IF EXISTS ${quoteIdent(temp)}`,
225
+ `CREATE TABLE ${quoteIdent(temp)} (
226
+ ${[...definitions, ...tableConstraints].join(",\n ")}
227
+ )`,
228
+ `INSERT INTO ${quoteIdent(temp)} (${columnList}) SELECT ${columnList} FROM ${quoteIdent(table)}`,
229
+ `DROP TABLE ${quoteIdent(table)}`,
230
+ `ALTER TABLE ${quoteIdent(temp)} RENAME TO ${quoteIdent(table)}`,
231
+ ...catalog.dependents
232
+ ];
233
+ }
234
+ function foreignKeyActions(fk) {
235
+ let out = "";
236
+ const onDelete = fk.on_delete?.toUpperCase();
237
+ const onUpdate = fk.on_update?.toUpperCase();
238
+ if (onDelete && onDelete !== "NO ACTION") out += ` ON DELETE ${onDelete}`;
239
+ if (onUpdate && onUpdate !== "NO ACTION") out += ` ON UPDATE ${onUpdate}`;
240
+ return out;
241
+ }
242
+ var EXPRESSION_KEYWORDS = /* @__PURE__ */ new Set([
243
+ "AND",
244
+ "ANY",
245
+ "ARRAY",
246
+ "IN",
247
+ "IS",
248
+ "NOT",
249
+ "NULL",
250
+ "OR",
251
+ "VALID"
252
+ ]);
253
+ function tokenizeCheck(definition) {
254
+ const tokens = [];
255
+ const pattern = /\s+|'(?:[^']|'')*'|"(?:[^"]|"")+"|::|[A-Za-z_][A-Za-z0-9_$]*|[0-9]+|<>|!=|<=|>=|[()[\],=<>]|./gy;
256
+ for (let match = pattern.exec(definition); match !== null; match = pattern.exec(definition)) {
257
+ if (match[0] === "") return null;
258
+ if (!/^\s+$/.test(match[0])) tokens.push(match[0]);
259
+ if (pattern.lastIndex >= definition.length) break;
260
+ }
261
+ return tokens;
262
+ }
263
+ function isWord(token) {
264
+ return token !== void 0 && /^[A-Za-z_][A-Za-z0-9_$]*$/.test(token);
265
+ }
266
+ function dropCasts(tokens) {
267
+ const out = [];
268
+ let i = 0;
269
+ while (i < tokens.length) {
270
+ if (tokens[i] !== "::") {
271
+ out.push(tokens[i]);
272
+ i++;
273
+ continue;
274
+ }
275
+ i++;
276
+ let words = 0;
277
+ while (isWord(tokens[i]) && !EXPRESSION_KEYWORDS.has(tokens[i].toUpperCase()) || /^"/.test(tokens[i] ?? "")) {
278
+ i++;
279
+ words++;
280
+ }
281
+ if (words === 0) return ["::invalid"];
282
+ if (tokens[i] === "(" && /^[0-9]+$/.test(tokens[i + 1] ?? "")) {
283
+ let j = i + 1;
284
+ while (/^[0-9]+$/.test(tokens[j] ?? "") || tokens[j] === ",") j++;
285
+ if (tokens[j] === ")") i = j + 1;
286
+ }
287
+ while (tokens[i] === "[" && tokens[i + 1] === "]") i += 2;
288
+ }
289
+ return out;
290
+ }
291
+ function unquoteIdentifier(token) {
292
+ if (/^"(?:[^"]|"")+"$/.test(token)) return token.slice(1, -1).replaceAll('""', '"');
293
+ if (isWord(token) && !EXPRESSION_KEYWORDS.has(token.toUpperCase())) return token;
294
+ return null;
295
+ }
296
+ function unquoteLiteral(token) {
297
+ if (token === void 0 || !/^'(?:[^']|'')*'$/.test(token)) return null;
298
+ return token.slice(1, -1).replaceAll("''", "'");
299
+ }
300
+ function parseEnumCheckDefinition(definition) {
301
+ const raw = tokenizeCheck(definition.trim());
302
+ if (raw === null || raw[0]?.toUpperCase() !== "CHECK") return null;
303
+ let tokens = dropCasts(raw.slice(1)).filter((token) => token !== "(" && token !== ")");
304
+ const upper = (index) => (tokens[index] ?? "").toUpperCase();
305
+ if (upper(tokens.length - 2) === "NOT" && upper(tokens.length - 1) === "VALID") {
306
+ tokens = tokens.slice(0, -2);
307
+ }
308
+ let column = null;
309
+ const values = [];
310
+ let allowsNull = false;
311
+ let i = 0;
312
+ const readLiteral = () => {
313
+ const value = unquoteLiteral(tokens[i]);
314
+ if (value === null) return false;
315
+ values.push(value);
316
+ i++;
317
+ return true;
318
+ };
319
+ const readList = () => {
320
+ if (!readLiteral()) return false;
321
+ while (tokens[i] === ",") {
322
+ i++;
323
+ if (!readLiteral()) return false;
324
+ }
325
+ return true;
326
+ };
327
+ for (; ; ) {
328
+ const name = unquoteIdentifier(tokens[i] ?? "");
329
+ if (name === null || column !== null && name !== column) return null;
330
+ column = name;
331
+ i++;
332
+ if (tokens[i] === "=" && upper(i + 1) === "ANY" && upper(i + 2) === "ARRAY") {
333
+ if (tokens[i + 3] !== "[") return null;
334
+ i += 4;
335
+ if (!readList() || tokens[i] !== "]") return null;
336
+ i++;
337
+ } else if (tokens[i] === "=") {
338
+ i++;
339
+ if (!readLiteral()) return null;
340
+ } else if (upper(i) === "IN") {
341
+ i++;
342
+ if (!readList()) return null;
343
+ } else if (upper(i) === "IS" && upper(i + 1) === "NULL") {
344
+ allowsNull = true;
345
+ i += 2;
346
+ } else {
347
+ return null;
348
+ }
349
+ if (i === tokens.length) break;
350
+ if (upper(i) !== "OR") return null;
351
+ i++;
352
+ }
353
+ if (column === null || values.length === 0) return null;
354
+ return { column, values, allowsNull };
355
+ }
356
+ function isPostgresEnumCheckDefinition(definition) {
357
+ return parseEnumCheckDefinition(definition) !== null;
358
+ }
359
+ function isAnyArrayCheck(definition) {
360
+ return /^CHECK\s*\(\(*\s*(?:"(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)(?:\)?::[A-Za-z ]+)?\s*=\s*ANY\s*\(/i.test(
361
+ definition.trim()
362
+ );
363
+ }
364
+ function sqlText(value) {
365
+ return `'${value.replaceAll("'", "''")}'`;
366
+ }
367
+ async function readSqliteTableCatalog(query, table) {
368
+ const master = await query(
369
+ `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ${sqlText(table)}`
370
+ );
371
+ const tableSql = master[0]?.sql;
372
+ if (typeof tableSql !== "string") return null;
373
+ const columns = await query(
374
+ `SELECT cid, name, type, "notnull" AS "notnull", dflt_value, pk FROM pragma_table_info(${sqlText(table)})`
375
+ );
376
+ const foreignKeys = await query(
377
+ `SELECT id, seq, "table" AS "table", "from" AS "from", "to" AS "to", on_update, on_delete FROM pragma_foreign_key_list(${sqlText(table)})`
378
+ );
379
+ const uniqueConstraints = [];
380
+ const indexes = await query(
381
+ `SELECT name FROM pragma_index_list(${sqlText(table)}) WHERE origin = 'u'`
382
+ );
383
+ for (const index of indexes) {
384
+ const info = await query(
385
+ `SELECT name FROM pragma_index_info(${sqlText(String(index.name))}) ORDER BY seqno`
386
+ );
387
+ uniqueConstraints.push(info.map((row) => String(row.name)));
388
+ }
389
+ const dependents = await query(
390
+ `SELECT sql FROM sqlite_master WHERE type IN ('index', 'trigger') AND tbl_name = ${sqlText(table)} AND sql IS NOT NULL ORDER BY type, name`
391
+ );
392
+ return {
393
+ table,
394
+ sql: tableSql,
395
+ columns: columns.map((c) => ({
396
+ cid: Number(c.cid),
397
+ name: String(c.name),
398
+ type: typeof c.type === "string" ? c.type : "",
399
+ notnull: Number(c.notnull),
400
+ dflt_value: typeof c.dflt_value === "string" ? c.dflt_value : null,
401
+ pk: Number(c.pk)
402
+ })),
403
+ foreignKeys: foreignKeys.map((fk) => ({
404
+ id: Number(fk.id),
405
+ seq: Number(fk.seq),
406
+ table: String(fk.table),
407
+ from: String(fk.from),
408
+ to: typeof fk.to === "string" ? fk.to : null,
409
+ on_update: String(fk.on_update ?? "NO ACTION"),
410
+ on_delete: String(fk.on_delete ?? "NO ACTION")
411
+ })),
412
+ uniqueConstraints,
413
+ dependents: dependents.map((row) => String(row.sql))
414
+ };
415
+ }
416
+ async function planSqliteConstraintRelaxation(query, tables, enumColumnsByTable = {}) {
417
+ const statements = [];
418
+ for (const table of tables) {
419
+ const catalog = await readSqliteTableCatalog(query, table);
420
+ if (catalog) {
421
+ statements.push(...sqliteConstraintRelaxationStatements(catalog, enumColumnsByTable[table]));
422
+ }
423
+ }
424
+ return statements;
425
+ }
426
+ async function planPostgresConstraintRelaxation(query, fieldsByTable, enumFieldsByTable = {}) {
427
+ const tables = Object.keys(fieldsByTable);
428
+ if (tables.length === 0) return [];
429
+ const tableList = tables.map(sqlText).join(", ");
430
+ const statements = [];
431
+ const checks = await query(
432
+ `SELECT rel.relname::text AS table_name, con.conname::text AS name, pg_get_constraintdef(con.oid) AS definition,
433
+ (SELECT array_agg(att.attname::text) FROM pg_attribute att WHERE att.attrelid = con.conrelid AND att.attnum = ANY (con.conkey)) AS columns
434
+ FROM pg_constraint con
435
+ JOIN pg_class rel ON rel.oid = con.conrelid
436
+ JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
437
+ WHERE con.contype = 'c' AND nsp.nspname = current_schema() AND rel.relname IN (${tableList})
438
+ ORDER BY rel.relname, con.conname`
439
+ );
440
+ for (const check of checks) {
441
+ const columns = Array.isArray(check.columns) ? check.columns.map(String) : [];
442
+ const column = columns[0];
443
+ if (columns.length !== 1 || column === void 0 || isKoraInternalColumn(column)) continue;
444
+ const shape = parseEnumCheckDefinition(String(check.definition));
445
+ if (shape === null || shape.column !== column) continue;
446
+ const table = String(check.table_name);
447
+ const enumColumns = enumFieldsByTable[table] ?? fieldsByTable[table] ?? [];
448
+ if (!enumColumns.includes(column) && !isAnyArrayCheck(String(check.definition))) continue;
449
+ statements.push(
450
+ `ALTER TABLE ${quoteIdent(String(check.table_name))} DROP CONSTRAINT IF EXISTS ${quoteIdent(String(check.name))}`
451
+ );
452
+ }
453
+ const notNull = await query(
454
+ `SELECT rel.relname::text AS table_name, att.attname::text AS column_name
455
+ FROM pg_attribute att
456
+ JOIN pg_class rel ON rel.oid = att.attrelid
457
+ JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
458
+ WHERE att.attnotnull AND att.attnum > 0 AND NOT att.attisdropped
459
+ AND rel.relkind = 'r' AND nsp.nspname = current_schema() AND rel.relname IN (${tableList})
460
+ ORDER BY rel.relname, att.attnum`
461
+ );
462
+ for (const row of notNull) {
463
+ const table = String(row.table_name);
464
+ const column = String(row.column_name);
465
+ if (isKoraInternalColumn(column)) continue;
466
+ if (!(fieldsByTable[table] ?? []).includes(column)) continue;
467
+ statements.push(
468
+ `ALTER TABLE ${quoteIdent(table)} ALTER COLUMN ${quoteIdent(column)} DROP NOT NULL`
469
+ );
470
+ }
471
+ return statements;
472
+ }
48
473
  export {
474
+ SCHEMA_CEILING_DIRECTIVE,
49
475
  SimpleEventEmitter,
50
476
  canonicalize,
477
+ collectionIndexName,
478
+ compareStamps,
51
479
  computeOperationId,
480
+ enumCheckConstraint,
481
+ isKoraInternalColumn,
482
+ isKoraSqliteEnumCheck,
483
+ isPostgresEnumCheckDefinition,
484
+ legacyCollectionIndexName,
485
+ parseEnumCheckDefinition,
486
+ parseSqliteCheckConstraints,
487
+ planField,
488
+ planPostgresConstraintRelaxation,
489
+ planSqliteConstraintRelaxation,
490
+ readSqliteTableCatalog,
491
+ sqlDefaultLiteral,
492
+ sqlStringLiteral,
493
+ sqliteCheckConstraintDefinition,
494
+ sqliteCheckReferencesColumn,
495
+ sqliteConstraintRelaxationStatements,
496
+ sqliteTableNeedsRelaxation,
497
+ stampOf,
52
498
  topologicalSort,
53
499
  validateOperationParams
54
500
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/events/event-emitter.ts"],"sourcesContent":["import type { KoraEventByType, KoraEventEmitter, KoraEventListener, KoraEventType } from './events'\n\ntype AnyListener = (event: never) => void\n\n/**\n * Concrete implementation of KoraEventEmitter.\n * Simple, synchronous event emitter for internal use across @kora packages.\n *\n * @example\n * ```typescript\n * const emitter = new SimpleEventEmitter()\n * const unsub = emitter.on('operation:created', (event) => {\n * console.log(event.operation.id)\n * })\n * emitter.emit({ type: 'operation:created', operation: someOp })\n * unsub() // unsubscribe\n * ```\n */\nexport class SimpleEventEmitter implements KoraEventEmitter {\n\tprivate listeners = new Map<string, Set<AnyListener>>()\n\n\ton<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): () => void {\n\t\tlet set = this.listeners.get(type)\n\t\tif (!set) {\n\t\t\tset = new Set()\n\t\t\tthis.listeners.set(type, set)\n\t\t}\n\t\tset.add(listener as AnyListener)\n\n\t\treturn () => {\n\t\t\tthis.off(type, listener)\n\t\t}\n\t}\n\n\toff<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): void {\n\t\tconst set = this.listeners.get(type)\n\t\tif (set) {\n\t\t\tset.delete(listener as AnyListener)\n\t\t\tif (set.size === 0) {\n\t\t\t\tthis.listeners.delete(type)\n\t\t\t}\n\t\t}\n\t}\n\n\temit<T extends KoraEventType>(event: KoraEventByType<T>): void {\n\t\tconst set = this.listeners.get(event.type)\n\t\tif (!set) return\n\t\tfor (const listener of set) {\n\t\t\t;(listener as (event: KoraEventByType<T>) => void)(event)\n\t\t}\n\t}\n\n\t/** Remove all listeners for all event types. */\n\tclear(): void {\n\t\tthis.listeners.clear()\n\t}\n\n\t/** Get the number of listeners for a specific event type. */\n\tlistenerCount(type: KoraEventType): number {\n\t\treturn this.listeners.get(type)?.size ?? 0\n\t}\n}\n"],"mappings":";;;;;;;;AAkBO,IAAM,qBAAN,MAAqD;AAAA,EACnD,YAAY,oBAAI,IAA8B;AAAA,EAEtD,GAA4B,MAAS,UAA4C;AAChF,QAAI,MAAM,KAAK,UAAU,IAAI,IAAI;AACjC,QAAI,CAAC,KAAK;AACT,YAAM,oBAAI,IAAI;AACd,WAAK,UAAU,IAAI,MAAM,GAAG;AAAA,IAC7B;AACA,QAAI,IAAI,QAAuB;AAE/B,WAAO,MAAM;AACZ,WAAK,IAAI,MAAM,QAAQ;AAAA,IACxB;AAAA,EACD;AAAA,EAEA,IAA6B,MAAS,UAAsC;AAC3E,UAAM,MAAM,KAAK,UAAU,IAAI,IAAI;AACnC,QAAI,KAAK;AACR,UAAI,OAAO,QAAuB;AAClC,UAAI,IAAI,SAAS,GAAG;AACnB,aAAK,UAAU,OAAO,IAAI;AAAA,MAC3B;AAAA,IACD;AAAA,EACD;AAAA,EAEA,KAA8B,OAAiC;AAC9D,UAAM,MAAM,KAAK,UAAU,IAAI,MAAM,IAAI;AACzC,QAAI,CAAC,IAAK;AACV,eAAW,YAAY,KAAK;AAC3B;AAAC,MAAC,SAAiD,KAAK;AAAA,IACzD;AAAA,EACD;AAAA;AAAA,EAGA,QAAc;AACb,SAAK,UAAU,MAAM;AAAA,EACtB;AAAA;AAAA,EAGA,cAAc,MAA6B;AAC1C,WAAO,KAAK,UAAU,IAAI,IAAI,GAAG,QAAQ;AAAA,EAC1C;AACD;","names":[]}
1
+ {"version":3,"sources":["../src/events/event-emitter.ts","../src/schema/constraint-relaxation.ts"],"sourcesContent":["import type { KoraEventByType, KoraEventEmitter, KoraEventListener, KoraEventType } from './events'\n\ntype AnyListener = (event: never) => void\n\n/**\n * Concrete implementation of KoraEventEmitter.\n * Simple, synchronous event emitter for internal use across @kora packages.\n *\n * @example\n * ```typescript\n * const emitter = new SimpleEventEmitter()\n * const unsub = emitter.on('operation:created', (event) => {\n * console.log(event.operation.id)\n * })\n * emitter.emit({ type: 'operation:created', operation: someOp })\n * unsub() // unsubscribe\n * ```\n */\nexport class SimpleEventEmitter implements KoraEventEmitter {\n\tprivate listeners = new Map<string, Set<AnyListener>>()\n\n\ton<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): () => void {\n\t\tlet set = this.listeners.get(type)\n\t\tif (!set) {\n\t\t\tset = new Set()\n\t\t\tthis.listeners.set(type, set)\n\t\t}\n\t\tset.add(listener as AnyListener)\n\n\t\treturn () => {\n\t\t\tthis.off(type, listener)\n\t\t}\n\t}\n\n\toff<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): void {\n\t\tconst set = this.listeners.get(type)\n\t\tif (set) {\n\t\t\tset.delete(listener as AnyListener)\n\t\t\tif (set.size === 0) {\n\t\t\t\tthis.listeners.delete(type)\n\t\t\t}\n\t\t}\n\t}\n\n\temit<T extends KoraEventType>(event: KoraEventByType<T>): void {\n\t\tconst set = this.listeners.get(event.type)\n\t\tif (!set) return\n\t\tfor (const listener of set) {\n\t\t\t;(listener as (event: KoraEventByType<T>) => void)(event)\n\t\t}\n\t}\n\n\t/** Remove all listeners for all event types. */\n\tclear(): void {\n\t\tthis.listeners.clear()\n\t}\n\n\t/** Get the number of listeners for a specific event type. */\n\tlistenerCount(type: KoraEventType): number {\n\t\treturn this.listeners.get(type)?.size ?? 0\n\t}\n}\n","import { quoteIdent } from './quote-ident'\n\n/**\n * Value-domain rules live in validation only (RT-101).\n *\n * Kora's generated DDL used to restate two schema rules as storage constraints: an enum\n * field's `CHECK (col IN (...))` and a required field's `NOT NULL`. The value domain\n * already enforces both at write time on every replica, and storage constraints cannot\n * evolve with the schema (SQLite cannot alter a column constraint): an enum value added\n * by a schema upgrade was valid everywhere but refused by every existing table. The DDL\n * no longer emits them, and existing tables are relaxed once, by these helpers.\n *\n * Kora's own bookkeeping columns (`id` and every `_`-prefixed column) keep their\n * constraints: they are written by Kora only and never evolve with the app schema.\n */\n\n/** A row of `PRAGMA table_info(<table>)`. */\nexport interface SqliteColumnInfo {\n\tcid: number\n\tname: string\n\ttype: string\n\tnotnull: number\n\tdflt_value: string | null\n\tpk: number\n}\n\n/** A row of `PRAGMA foreign_key_list(<table>)`. */\nexport interface SqliteForeignKeyInfo {\n\tid: number\n\tseq: number\n\ttable: string\n\tfrom: string\n\tto: string | null\n\ton_update: string\n\ton_delete: string\n\tmatch?: string\n}\n\n/** What a SQLite table looks like, read from its catalog. */\nexport interface SqliteTableCatalog {\n\t/** Table name */\n\ttable: string\n\t/** The table's `CREATE TABLE` statement (`sqlite_master.sql`) */\n\tsql: string\n\tcolumns: readonly SqliteColumnInfo[]\n\tforeignKeys: readonly SqliteForeignKeyInfo[]\n\t/** Column lists of the table's `UNIQUE` constraints (`index_list` origin `u`) */\n\tuniqueConstraints: readonly (readonly string[])[]\n\t/** `CREATE INDEX` / `CREATE TRIGGER` statements on the table (`sqlite_master.sql`) */\n\tdependents: readonly string[]\n}\n\n/** Columns Kora writes itself; their constraints are kept. */\nexport function isKoraInternalColumn(name: string): boolean {\n\treturn name === 'id' || name.startsWith('_')\n}\n\n/** One `CHECK` constraint of a SQLite `CREATE TABLE` statement. */\nexport interface SqliteCheckConstraint {\n\t/** The constraint name (`CONSTRAINT <name> CHECK ...`), unquoted, or null */\n\tname: string | null\n\t/** The checked expression, verbatim (the text between the parentheses) */\n\texpression: string\n\t/** The column whose definition carries the check (a column constraint), or null */\n\tcolumn: string | null\n}\n\ninterface SqlToken {\n\ttext: string\n\tstart: number\n\tend: number\n}\n\n/** Tokens of a SQLite statement: literals and quoted identifiers whole, comments skipped. */\nfunction scanSqlTokens(sql: string): SqlToken[] {\n\tconst tokens: SqlToken[] = []\n\tconst pattern =\n\t\t/\\s+|--[^\\n]*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)|'(?:[^']|'')*'?|\"(?:[^\"]|\"\")*\"?|`(?:[^`]|``)*`?|\\[[^\\]]*\\]?|[A-Za-z_][A-Za-z0-9_$]*|[0-9]+(?:\\.[0-9]+)?|[(),]|[^\\s(),'\"`[A-Za-z0-9_]+/g\n\tfor (let match = pattern.exec(sql); match !== null; match = pattern.exec(sql)) {\n\t\tconst text = match[0]\n\t\tif (/^\\s/.test(text) || text.startsWith('--') || text.startsWith('/*')) continue\n\t\ttokens.push({ text, start: match.index, end: match.index + text.length })\n\t}\n\treturn tokens\n}\n\nfunction unquoteSqliteName(token: string | undefined): string | null {\n\tif (token === undefined) return null\n\tif (/^\"(?:[^\"]|\"\")*\"$/.test(token)) return token.slice(1, -1).replaceAll('\"\"', '\"')\n\tif (/^`(?:[^`]|``)*`$/.test(token)) return token.slice(1, -1).replaceAll('``', '`')\n\tif (/^\\[[^\\]]*\\]$/.test(token)) return token.slice(1, -1)\n\tif (/^'(?:[^']|'')*'$/.test(token)) return token.slice(1, -1).replaceAll(\"''\", \"'\")\n\tif (/^[A-Za-z_][A-Za-z0-9_$]*$/.test(token)) return token\n\treturn null\n}\n\nconst TABLE_CONSTRAINT_KEYWORDS: ReadonlySet<string> = new Set([\n\t'CONSTRAINT',\n\t'PRIMARY',\n\t'UNIQUE',\n\t'CHECK',\n\t'FOREIGN',\n])\n\n/**\n * Every `CHECK` constraint of a SQLite `CREATE TABLE` statement, column and table\n * constraints alike, with the expression verbatim (RT-111). The catalog pragmas do not\n * report checks, so a table rebuild must carry them over from the statement itself.\n *\n * @param createTableSql - The table's `CREATE TABLE` statement (`sqlite_master.sql`)\n * @returns The checks in statement order\n */\nexport function parseSqliteCheckConstraints(createTableSql: string): SqliteCheckConstraint[] {\n\tconst tokens = scanSqlTokens(createTableSql)\n\tconst open = tokens.findIndex((token) => token.text === '(')\n\tif (open < 0) return []\n\t// Split the column/constraint list into its top-level definitions.\n\tconst definitions: SqlToken[][] = []\n\tlet current: SqlToken[] = []\n\tlet depth = 0\n\tfor (let i = open + 1; i < tokens.length; i++) {\n\t\tconst token = tokens[i] as SqlToken\n\t\tif (token.text === '(') depth++\n\t\tif (token.text === ')') {\n\t\t\tif (depth === 0) break\n\t\t\tdepth--\n\t\t}\n\t\tif (token.text === ',' && depth === 0) {\n\t\t\tdefinitions.push(current)\n\t\t\tcurrent = []\n\t\t\tcontinue\n\t\t}\n\t\tcurrent.push(token)\n\t}\n\tif (current.length > 0) definitions.push(current)\n\n\tconst checks: SqliteCheckConstraint[] = []\n\tfor (const definition of definitions) {\n\t\tconst first = definition[0]\n\t\tif (first === undefined) continue\n\t\tconst column = TABLE_CONSTRAINT_KEYWORDS.has(first.text.toUpperCase())\n\t\t\t? null\n\t\t\t: unquoteSqliteName(first.text)\n\t\tlet level = 0\n\t\tfor (let i = 0; i < definition.length; i++) {\n\t\t\tconst token = definition[i] as SqlToken\n\t\t\tif (token.text === '(') level++\n\t\t\telse if (token.text === ')') level--\n\t\t\tif (level !== 0 || token.text.toUpperCase() !== 'CHECK') continue\n\t\t\tconst opening = definition[i + 1]\n\t\t\tif (opening?.text !== '(') continue\n\t\t\tlet inner = 0\n\t\t\tlet closing: SqlToken | undefined\n\t\t\tfor (let j = i + 1; j < definition.length; j++) {\n\t\t\t\tconst candidate = definition[j] as SqlToken\n\t\t\t\tif (candidate.text === '(') inner++\n\t\t\t\tif (candidate.text === ')' && --inner === 0) {\n\t\t\t\t\tclosing = candidate\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (closing === undefined) continue\n\t\t\tconst named =\n\t\t\t\ti >= 2 && (definition[i - 2] as SqlToken).text.toUpperCase() === 'CONSTRAINT'\n\t\t\t\t\t? unquoteSqliteName((definition[i - 1] as SqlToken).text)\n\t\t\t\t\t: null\n\t\t\tchecks.push({\n\t\t\t\tname: named,\n\t\t\t\texpression: createTableSql.slice(opening.end, closing.start).trim(),\n\t\t\t\tcolumn,\n\t\t\t})\n\t\t}\n\t}\n\treturn checks\n}\n\n/**\n * Whether a check is one of Kora's own value-domain checks (beta.12 and earlier DDL):\n * an enum check (see {@link parseEnumCheckDefinition}) on one column that is not Kora's\n * own and, when `enumColumns` is given, is an enum field of the schema. A check of any\n * other shape, or on another column, was added by hand and is kept (RT-111).\n *\n * @param check - A check of the table\n * @param enumColumns - The table's enum fields (undefined: any enum-shaped check counts)\n */\nexport function isKoraSqliteEnumCheck(\n\tcheck: SqliteCheckConstraint,\n\tenumColumns?: readonly string[],\n): boolean {\n\tconst shape = parseEnumCheckDefinition(`CHECK (${check.expression})`)\n\tif (shape === null || isKoraInternalColumn(shape.column)) return false\n\tif (check.column !== null && check.column !== shape.column) return false\n\treturn enumColumns === undefined || enumColumns.includes(shape.column)\n}\n\n/**\n * Whether a table still carries a value-domain constraint of Kora's: an enum `CHECK` on\n * an enum field (see {@link isKoraSqliteEnumCheck}), or `NOT NULL` on a column that is not\n * Kora's own. Checks added by hand never make a table need relaxation.\n *\n * @param catalog - The table as read from SQLite's catalog\n * @param enumColumns - The table's enum fields (undefined: any enum-shaped check counts)\n */\nexport function sqliteTableNeedsRelaxation(\n\tcatalog: SqliteTableCatalog,\n\tenumColumns?: readonly string[],\n): boolean {\n\tif (\n\t\tparseSqliteCheckConstraints(catalog.sql).some((check) =>\n\t\t\tisKoraSqliteEnumCheck(check, enumColumns),\n\t\t)\n\t) {\n\t\treturn true\n\t}\n\treturn catalog.columns.some(\n\t\t(column) => column.notnull === 1 && !isKoraInternalColumn(column.name),\n\t)\n}\n\n/**\n * Whether a check names any of `columns` (its own column, or an identifier in its\n * expression). A rebuild that drops a column cannot keep a check that reads it.\n *\n * @param check - A check of the table\n * @param columns - Column names\n */\nexport function sqliteCheckReferencesColumn(\n\tcheck: SqliteCheckConstraint,\n\tcolumns: ReadonlySet<string>,\n): boolean {\n\tif (check.column !== null && columns.has(check.column)) return true\n\treturn scanSqlTokens(check.expression).some((token) => {\n\t\tif (token.text.startsWith(\"'\")) return false\n\t\tconst name = unquoteSqliteName(token.text)\n\t\treturn name !== null && columns.has(name)\n\t})\n}\n\n/**\n * The table constraint that re-creates a kept check in a rebuilt table: a column check\n * becomes a table check (SQLite evaluates both the same way, per row).\n *\n * @param check - The check to keep\n */\nexport function sqliteCheckConstraintDefinition(check: SqliteCheckConstraint): string {\n\tconst name = check.name === null ? '' : `CONSTRAINT ${quoteIdent(check.name)} `\n\treturn `${name}CHECK (${check.expression})`\n}\n\n/**\n * Statements that rebuild a SQLite table without value-domain constraints (SQLite's\n * documented \"make other kinds of table schema changes\" procedure), keeping every row,\n * column (types and defaults), primary key, foreign key, `UNIQUE` constraint, index and\n * trigger, and every `CHECK` that is not one of Kora's enum checks (RT-111). Run them in\n * ONE transaction, with foreign key enforcement off when the database enforces foreign\n * keys (the drop of a referenced table would otherwise fail or fire its actions). Returns\n * an empty list when the table needs nothing, so running it again is a no-op\n * (idempotent); a rebuild interrupted mid-way rolls back with its transaction and runs\n * again at the next open (resumable).\n *\n * @param catalog - The table as read from SQLite's catalog\n * @param enumColumns - The table's enum fields, which decide which enum-shaped checks are\n * Kora's (undefined: any enum-shaped check on a non-internal column)\n * @returns The rebuild statements, or `[]`\n */\nexport function sqliteConstraintRelaxationStatements(\n\tcatalog: SqliteTableCatalog,\n\tenumColumns?: readonly string[],\n): string[] {\n\tif (!sqliteTableNeedsRelaxation(catalog, enumColumns)) return []\n\tconst table = catalog.table\n\tconst temp = `_kora_relax_${table}`\n\tconst ordered = [...catalog.columns].sort((a, b) => a.cid - b.cid)\n\tconst pkColumns = ordered.filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk)\n\n\tconst fkGroups = new Map<number, SqliteForeignKeyInfo[]>()\n\tfor (const fk of catalog.foreignKeys) {\n\t\tconst group = fkGroups.get(fk.id) ?? []\n\t\tgroup.push(fk)\n\t\tfkGroups.set(fk.id, group)\n\t}\n\tconst columnReferences = new Map<string, string>()\n\tconst tableConstraints: string[] = []\n\tfor (const group of fkGroups.values()) {\n\t\tgroup.sort((a, b) => a.seq - b.seq)\n\t\tconst first = group[0]\n\t\tif (!first) continue\n\t\tconst actions = foreignKeyActions(first)\n\t\tconst target = group.every((fk) => fk.to !== null && fk.to !== '')\n\t\t\t? `${quoteIdent(first.table)}(${group.map((fk) => quoteIdent(fk.to ?? '')).join(', ')})`\n\t\t\t: quoteIdent(first.table)\n\t\tif (group.length === 1) {\n\t\t\tcolumnReferences.set(first.from, `REFERENCES ${target}${actions}`)\n\t\t} else {\n\t\t\ttableConstraints.push(\n\t\t\t\t`FOREIGN KEY (${group.map((fk) => quoteIdent(fk.from)).join(', ')}) REFERENCES ${target}${actions}`,\n\t\t\t)\n\t\t}\n\t}\n\tif (pkColumns.length > 1) {\n\t\ttableConstraints.unshift(`PRIMARY KEY (${pkColumns.map((c) => quoteIdent(c.name)).join(', ')})`)\n\t}\n\tfor (const unique of catalog.uniqueConstraints) {\n\t\ttableConstraints.push(`UNIQUE (${unique.map(quoteIdent).join(', ')})`)\n\t}\n\tfor (const check of parseSqliteCheckConstraints(catalog.sql)) {\n\t\tif (!isKoraSqliteEnumCheck(check, enumColumns)) {\n\t\t\ttableConstraints.push(sqliteCheckConstraintDefinition(check))\n\t\t}\n\t}\n\n\tconst definitions = ordered.map((column) => {\n\t\tconst parts = [quoteIdent(column.name)]\n\t\tif (column.type) parts.push(column.type)\n\t\tif (pkColumns.length === 1 && column.pk === 1) parts.push('PRIMARY KEY')\n\t\tif (column.notnull === 1 && isKoraInternalColumn(column.name)) parts.push('NOT NULL')\n\t\tif (column.dflt_value !== null) parts.push(`DEFAULT (${column.dflt_value})`)\n\t\tconst reference = columnReferences.get(column.name)\n\t\tif (reference) parts.push(reference)\n\t\treturn parts.join(' ')\n\t})\n\n\tconst columnList = ordered.map((column) => quoteIdent(column.name)).join(', ')\n\treturn [\n\t\t`DROP TABLE IF EXISTS ${quoteIdent(temp)}`,\n\t\t`CREATE TABLE ${quoteIdent(temp)} (\\n ${[...definitions, ...tableConstraints].join(',\\n ')}\\n)`,\n\t\t`INSERT INTO ${quoteIdent(temp)} (${columnList}) SELECT ${columnList} FROM ${quoteIdent(table)}`,\n\t\t`DROP TABLE ${quoteIdent(table)}`,\n\t\t`ALTER TABLE ${quoteIdent(temp)} RENAME TO ${quoteIdent(table)}`,\n\t\t...catalog.dependents,\n\t]\n}\n\nfunction foreignKeyActions(fk: SqliteForeignKeyInfo): string {\n\tlet out = ''\n\tconst onDelete = fk.on_delete?.toUpperCase()\n\tconst onUpdate = fk.on_update?.toUpperCase()\n\tif (onDelete && onDelete !== 'NO ACTION') out += ` ON DELETE ${onDelete}`\n\tif (onUpdate && onUpdate !== 'NO ACTION') out += ` ON UPDATE ${onUpdate}`\n\treturn out\n}\n\n/** A `CHECK` that restricts one column to a list of string literals (an enum check). */\nexport interface EnumCheckShape {\n\t/** The column, unquoted */\n\tcolumn: string\n\t/** The allowed values, unquoted */\n\tvalues: string[]\n\t/** The check also allows NULL (`... OR col IS NULL`) */\n\tallowsNull: boolean\n}\n\n/** Keywords that never belong to a cast's type name. */\nconst EXPRESSION_KEYWORDS: ReadonlySet<string> = new Set([\n\t'AND',\n\t'ANY',\n\t'ARRAY',\n\t'IN',\n\t'IS',\n\t'NOT',\n\t'NULL',\n\t'OR',\n\t'VALID',\n])\n\nfunction tokenizeCheck(definition: string): string[] | null {\n\tconst tokens: string[] = []\n\tconst pattern =\n\t\t/\\s+|'(?:[^']|'')*'|\"(?:[^\"]|\"\")+\"|::|[A-Za-z_][A-Za-z0-9_$]*|[0-9]+|<>|!=|<=|>=|[()[\\],=<>]|./gy\n\tfor (let match = pattern.exec(definition); match !== null; match = pattern.exec(definition)) {\n\t\tif (match[0] === '') return null\n\t\tif (!/^\\s+$/.test(match[0])) tokens.push(match[0])\n\t\tif (pattern.lastIndex >= definition.length) break\n\t}\n\treturn tokens\n}\n\nfunction isWord(token: string | undefined): token is string {\n\treturn token !== undefined && /^[A-Za-z_][A-Za-z0-9_$]*$/.test(token)\n}\n\n/** Drop `::type` casts (`::text`, `::character varying`, `::text[]`, `::varchar(20)`). */\nfunction dropCasts(tokens: string[]): string[] {\n\tconst out: string[] = []\n\tlet i = 0\n\twhile (i < tokens.length) {\n\t\tif (tokens[i] !== '::') {\n\t\t\tout.push(tokens[i] as string)\n\t\t\ti++\n\t\t\tcontinue\n\t\t}\n\t\ti++\n\t\tlet words = 0\n\t\twhile (\n\t\t\t(isWord(tokens[i]) && !EXPRESSION_KEYWORDS.has((tokens[i] as string).toUpperCase())) ||\n\t\t\t/^\"/.test(tokens[i] ?? '')\n\t\t) {\n\t\t\ti++\n\t\t\twords++\n\t\t}\n\t\tif (words === 0) return ['::invalid']\n\t\tif (tokens[i] === '(' && /^[0-9]+$/.test(tokens[i + 1] ?? '')) {\n\t\t\tlet j = i + 1\n\t\t\twhile (/^[0-9]+$/.test(tokens[j] ?? '') || tokens[j] === ',') j++\n\t\t\tif (tokens[j] === ')') i = j + 1\n\t\t}\n\t\twhile (tokens[i] === '[' && tokens[i + 1] === ']') i += 2\n\t}\n\treturn out\n}\n\nfunction unquoteIdentifier(token: string): string | null {\n\tif (/^\"(?:[^\"]|\"\")+\"$/.test(token)) return token.slice(1, -1).replaceAll('\"\"', '\"')\n\tif (isWord(token) && !EXPRESSION_KEYWORDS.has(token.toUpperCase())) return token\n\treturn null\n}\n\nfunction unquoteLiteral(token: string | undefined): string | null {\n\tif (token === undefined || !/^'(?:[^']|'')*'$/.test(token)) return null\n\treturn token.slice(1, -1).replaceAll(\"''\", \"'\")\n}\n\n/**\n * Parse a `CHECK` constraint definition as an enum check, structurally: one column\n * compared to string literals, in any form Postgres normalizes `col IN (...)` to\n * (`col = ANY (ARRAY[...])`, the single-value `col = 'x'`, casts such as\n * `(col)::text = ANY ((ARRAY['x'::character varying])::text[])`), a disjunction of\n * equalities (`col = 'a' OR col = 'b'`), the SQLite source form `col IN ('a', 'b')`, an\n * optional `OR col IS NULL`, and a trailing `NOT VALID`. Anything else (another operator,\n * a function call, two columns, `AND`, `NOT`) is not an enum check and returns null.\n *\n * @param definition - The constraint definition (`pg_get_constraintdef`, or SQLite DDL)\n * @returns The column and values, or null when the check has another shape\n */\nexport function parseEnumCheckDefinition(definition: string): EnumCheckShape | null {\n\tconst raw = tokenizeCheck(definition.trim())\n\tif (raw === null || raw[0]?.toUpperCase() !== 'CHECK') return null\n\tlet tokens = dropCasts(raw.slice(1)).filter((token) => token !== '(' && token !== ')')\n\tconst upper = (index: number): string => (tokens[index] ?? '').toUpperCase()\n\tif (upper(tokens.length - 2) === 'NOT' && upper(tokens.length - 1) === 'VALID') {\n\t\ttokens = tokens.slice(0, -2)\n\t}\n\tlet column: string | null = null\n\tconst values: string[] = []\n\tlet allowsNull = false\n\tlet i = 0\n\tconst readLiteral = (): boolean => {\n\t\tconst value = unquoteLiteral(tokens[i])\n\t\tif (value === null) return false\n\t\tvalues.push(value)\n\t\ti++\n\t\treturn true\n\t}\n\tconst readList = (): boolean => {\n\t\tif (!readLiteral()) return false\n\t\twhile (tokens[i] === ',') {\n\t\t\ti++\n\t\t\tif (!readLiteral()) return false\n\t\t}\n\t\treturn true\n\t}\n\tfor (;;) {\n\t\tconst name = unquoteIdentifier(tokens[i] ?? '')\n\t\tif (name === null || (column !== null && name !== column)) return null\n\t\tcolumn = name\n\t\ti++\n\t\tif (tokens[i] === '=' && upper(i + 1) === 'ANY' && upper(i + 2) === 'ARRAY') {\n\t\t\tif (tokens[i + 3] !== '[') return null\n\t\t\ti += 4\n\t\t\tif (!readList() || tokens[i] !== ']') return null\n\t\t\ti++\n\t\t} else if (tokens[i] === '=') {\n\t\t\ti++\n\t\t\tif (!readLiteral()) return null\n\t\t} else if (upper(i) === 'IN') {\n\t\t\ti++\n\t\t\tif (!readList()) return null\n\t\t} else if (upper(i) === 'IS' && upper(i + 1) === 'NULL') {\n\t\t\tallowsNull = true\n\t\t\ti += 2\n\t\t} else {\n\t\t\treturn null\n\t\t}\n\t\tif (i === tokens.length) break\n\t\tif (upper(i) !== 'OR') return null\n\t\ti++\n\t}\n\tif (column === null || values.length === 0) return null\n\treturn { column, values, allowsNull }\n}\n\n/**\n * Whether a Postgres `CHECK` constraint definition (`pg_get_constraintdef`) has the shape\n * of a Kora enum check (see {@link parseEnumCheckDefinition}). Other checks (added by hand)\n * are left alone.\n *\n * @param definition - The constraint definition text\n */\nexport function isPostgresEnumCheckDefinition(definition: string): boolean {\n\treturn parseEnumCheckDefinition(definition) !== null\n}\n\n/** The `col = ANY (ARRAY[...])` form only: what every multi-value beta.12 enum check became. */\nfunction isAnyArrayCheck(definition: string): boolean {\n\treturn /^CHECK\\s*\\(\\(*\\s*(?:\"(?:[^\"]|\"\")+\"|[A-Za-z_][A-Za-z0-9_$]*)(?:\\)?::[A-Za-z ]+)?\\s*=\\s*ANY\\s*\\(/i.test(\n\t\tdefinition.trim(),\n\t)\n}\n\n/** Runs a read-only SQL statement and returns its rows (an adapter's or a transaction's query). */\nexport type SqliteQueryFn = (sql: string) => Promise<Array<Record<string, unknown>>>\n\nfunction sqlText(value: string): string {\n\treturn `'${value.replaceAll(\"'\", \"''\")}'`\n}\n\n/**\n * Read a SQLite table's catalog through `query`, or null when the table does not exist.\n *\n * @param query - Runs a SELECT/PRAGMA and returns rows\n * @param table - The table name\n */\nexport async function readSqliteTableCatalog(\n\tquery: SqliteQueryFn,\n\ttable: string,\n): Promise<SqliteTableCatalog | null> {\n\tconst master = await query(\n\t\t`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ${sqlText(table)}`,\n\t)\n\tconst tableSql = master[0]?.sql\n\tif (typeof tableSql !== 'string') return null\n\tconst columns = await query(\n\t\t`SELECT cid, name, type, \"notnull\" AS \"notnull\", dflt_value, pk FROM pragma_table_info(${sqlText(table)})`,\n\t)\n\tconst foreignKeys = await query(\n\t\t`SELECT id, seq, \"table\" AS \"table\", \"from\" AS \"from\", \"to\" AS \"to\", on_update, on_delete FROM pragma_foreign_key_list(${sqlText(table)})`,\n\t)\n\tconst uniqueConstraints: string[][] = []\n\tconst indexes = await query(\n\t\t`SELECT name FROM pragma_index_list(${sqlText(table)}) WHERE origin = 'u'`,\n\t)\n\tfor (const index of indexes) {\n\t\tconst info = await query(\n\t\t\t`SELECT name FROM pragma_index_info(${sqlText(String(index.name))}) ORDER BY seqno`,\n\t\t)\n\t\tuniqueConstraints.push(info.map((row) => String(row.name)))\n\t}\n\tconst dependents = await query(\n\t\t`SELECT sql FROM sqlite_master WHERE type IN ('index', 'trigger') AND tbl_name = ${sqlText(table)} AND sql IS NOT NULL ORDER BY type, name`,\n\t)\n\treturn {\n\t\ttable,\n\t\tsql: tableSql,\n\t\tcolumns: columns.map((c) => ({\n\t\t\tcid: Number(c.cid),\n\t\t\tname: String(c.name),\n\t\t\ttype: typeof c.type === 'string' ? c.type : '',\n\t\t\tnotnull: Number(c.notnull),\n\t\t\tdflt_value: typeof c.dflt_value === 'string' ? c.dflt_value : null,\n\t\t\tpk: Number(c.pk),\n\t\t})),\n\t\tforeignKeys: foreignKeys.map((fk) => ({\n\t\t\tid: Number(fk.id),\n\t\t\tseq: Number(fk.seq),\n\t\t\ttable: String(fk.table),\n\t\t\tfrom: String(fk.from),\n\t\t\tto: typeof fk.to === 'string' ? fk.to : null,\n\t\t\ton_update: String(fk.on_update ?? 'NO ACTION'),\n\t\t\ton_delete: String(fk.on_delete ?? 'NO ACTION'),\n\t\t})),\n\t\tuniqueConstraints,\n\t\tdependents: dependents.map((row) => String(row.sql)),\n\t}\n}\n\n/**\n * Every statement that relaxes the given SQLite tables (one rebuild per table that still\n * carries a value-domain constraint; tables that do not exist or need nothing are\n * skipped). Run them in one transaction; see {@link sqliteConstraintRelaxationStatements}.\n *\n * @param query - Runs a SELECT/PRAGMA and returns rows (inside the caller's transaction)\n * @param tables - The collection tables to check\n * @param enumColumnsByTable - Each table's enum fields (only enum checks on them are\n * Kora's, RT-111); a table missing here treats any enum-shaped check as Kora's\n * @returns The statements, or `[]` when every table is already relaxed (idempotent)\n */\nexport async function planSqliteConstraintRelaxation(\n\tquery: SqliteQueryFn,\n\ttables: readonly string[],\n\tenumColumnsByTable: Readonly<Record<string, readonly string[]>> = {},\n): Promise<string[]> {\n\tconst statements: string[] = []\n\tfor (const table of tables) {\n\t\tconst catalog = await readSqliteTableCatalog(query, table)\n\t\tif (catalog) {\n\t\t\tstatements.push(...sqliteConstraintRelaxationStatements(catalog, enumColumnsByTable[table]))\n\t\t}\n\t}\n\treturn statements\n}\n\n/**\n * The `ALTER TABLE` statements that relax Postgres collection tables: drop every Kora enum\n * `CHECK` (one column compared to a literal list, see {@link isPostgresEnumCheckDefinition})\n * and every `NOT NULL` on a schema field. Kora's own columns, multi-column checks and\n * checks of any other shape (added by hand) are kept. Reads the catalog of the current\n * schema through `query`, so run it and the statements in one transaction.\n *\n * A check is matched structurally against the schema (RT-108): it is parsed as an enum\n * check ({@link parseEnumCheckDefinition}, every form Postgres normalizes `IN (...)` to,\n * including the single-value `col = 'x'`) and dropped when its one column is an enum field\n * of the table (`enumFieldsByTable`; without it, any schema field), or when it restricts\n * any other non-internal column to two or more literals (the multi-value shape every\n * beta.12 enum check had).\n *\n * @param query - Runs a SELECT and returns rows\n * @param fieldsByTable - Collection table name to its schema field names\n * @param enumFieldsByTable - Collection table name to its enum field names (before and\n * after the change); when a table is missing, every schema field counts as one\n * @returns The statements, or `[]` when nothing is left to relax (idempotent)\n */\nexport async function planPostgresConstraintRelaxation(\n\tquery: SqliteQueryFn,\n\tfieldsByTable: Readonly<Record<string, readonly string[]>>,\n\tenumFieldsByTable: Readonly<Record<string, readonly string[]>> = {},\n): Promise<string[]> {\n\tconst tables = Object.keys(fieldsByTable)\n\tif (tables.length === 0) return []\n\tconst tableList = tables.map(sqlText).join(', ')\n\tconst statements: string[] = []\n\tconst checks = await query(\n\t\t`SELECT rel.relname::text AS table_name, con.conname::text AS name, pg_get_constraintdef(con.oid) AS definition,\n\t\t\t(SELECT array_agg(att.attname::text) FROM pg_attribute att WHERE att.attrelid = con.conrelid AND att.attnum = ANY (con.conkey)) AS columns\n\t\tFROM pg_constraint con\n\t\tJOIN pg_class rel ON rel.oid = con.conrelid\n\t\tJOIN pg_namespace nsp ON nsp.oid = rel.relnamespace\n\t\tWHERE con.contype = 'c' AND nsp.nspname = current_schema() AND rel.relname IN (${tableList})\n\t\tORDER BY rel.relname, con.conname`,\n\t)\n\tfor (const check of checks) {\n\t\tconst columns = Array.isArray(check.columns) ? check.columns.map(String) : []\n\t\tconst column = columns[0]\n\t\tif (columns.length !== 1 || column === undefined || isKoraInternalColumn(column)) continue\n\t\tconst shape = parseEnumCheckDefinition(String(check.definition))\n\t\tif (shape === null || shape.column !== column) continue\n\t\tconst table = String(check.table_name)\n\t\tconst enumColumns = enumFieldsByTable[table] ?? fieldsByTable[table] ?? []\n\t\tif (!enumColumns.includes(column) && !isAnyArrayCheck(String(check.definition))) continue\n\t\tstatements.push(\n\t\t\t`ALTER TABLE ${quoteIdent(String(check.table_name))} DROP CONSTRAINT IF EXISTS ${quoteIdent(String(check.name))}`,\n\t\t)\n\t}\n\tconst notNull = await query(\n\t\t`SELECT rel.relname::text AS table_name, att.attname::text AS column_name\n\t\tFROM pg_attribute att\n\t\tJOIN pg_class rel ON rel.oid = att.attrelid\n\t\tJOIN pg_namespace nsp ON nsp.oid = rel.relnamespace\n\t\tWHERE att.attnotnull AND att.attnum > 0 AND NOT att.attisdropped\n\t\t\tAND rel.relkind = 'r' AND nsp.nspname = current_schema() AND rel.relname IN (${tableList})\n\t\tORDER BY rel.relname, att.attnum`,\n\t)\n\tfor (const row of notNull) {\n\t\tconst table = String(row.table_name)\n\t\tconst column = String(row.column_name)\n\t\tif (isKoraInternalColumn(column)) continue\n\t\tif (!(fieldsByTable[table] ?? []).includes(column)) continue\n\t\tstatements.push(\n\t\t\t`ALTER TABLE ${quoteIdent(table)} ALTER COLUMN ${quoteIdent(column)} DROP NOT NULL`,\n\t\t)\n\t}\n\treturn statements\n}\n"],"mappings":"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","names":[]}