@sequenceholdings/orm 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/authoring.cjs +284 -99
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +31 -8
- package/dist/authoring.d.ts +31 -8
- package/dist/authoring.js +282 -100
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +51 -3
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +838 -12
- package/dist/canonical.d.ts +838 -12
- package/dist/canonical.js +47 -4
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +5838 -4197
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +5838 -4197
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs +49 -32
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +49 -6
- package/dist/client.d.ts +49 -6
- package/dist/client.js +49 -32
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +1237 -263
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +57 -3
- package/dist/compiler.d.ts +57 -3
- package/dist/compiler.js +1230 -264
- package/dist/compiler.js.map +1 -1
- package/dist/{constants-YoFGjIBM.d.cts → constants-C6EG6Wq7.d.cts} +1 -1
- package/dist/{constants-YoFGjIBM.d.ts → constants-C6EG6Wq7.d.ts} +1 -1
- package/dist/definition-DjfC7Lll.d.cts +91 -0
- package/dist/definition-DjfC7Lll.d.ts +91 -0
- package/dist/dynamic-input.cjs +2 -1
- package/dist/dynamic-input.cjs.map +1 -1
- package/dist/dynamic-input.d.cts +9 -2
- package/dist/dynamic-input.d.ts +9 -2
- package/dist/dynamic-input.js +2 -1
- package/dist/dynamic-input.js.map +1 -1
- package/dist/index.cjs +1094 -321
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +479 -19
- package/dist/index.d.ts +479 -19
- package/dist/index.js +1083 -322
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +84 -10
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +138 -13
- package/dist/ir.d.ts +138 -13
- package/dist/ir.js +81 -11
- package/dist/ir.js.map +1 -1
- package/dist/predicate-v2.cjs +78 -9
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.d.cts +1 -1
- package/dist/predicate-v2.d.ts +1 -1
- package/dist/predicate-v2.js +78 -9
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +156 -21
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +8 -1
- package/dist/sdl.d.ts +8 -1
- package/dist/sdl.js +156 -21
- package/dist/sdl.js.map +1 -1
- package/package.json +5 -4
- package/src/codegen-gql/server-fns.cjs +2 -2
- package/templates/orm-namespace/AGENTS.md +10 -0
- package/templates/orm-namespace/CLAUDE.md +1 -84
- package/templates/orm-namespace/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -165,8 +165,8 @@ var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
|
165
165
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
166
166
|
var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
|
|
167
167
|
var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
168
|
-
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]
|
|
169
|
-
var SYNCED_SOURCE_PATTERN =
|
|
168
|
+
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
|
|
169
|
+
var SYNCED_SOURCE_PATTERN = QUALIFIED_TABLE_PATTERN;
|
|
170
170
|
var ORM_SCHEMA_PREFIX = "orm_";
|
|
171
171
|
var CONSUMER_ROLE_SUFFIX = "_consumer";
|
|
172
172
|
var ormSchemaName = (namespace) => `${ORM_SCHEMA_PREFIX}${namespace}`;
|
|
@@ -267,6 +267,9 @@ function registerNamespaceConfig(value) {
|
|
|
267
267
|
// src/authoring/table.ts
|
|
268
268
|
var TABLE_BRAND = "seq-table";
|
|
269
269
|
var LINK_BRAND = "seq-link";
|
|
270
|
+
function objectRef(namespace, table) {
|
|
271
|
+
return { kind: "seq-external-object", namespace, table };
|
|
272
|
+
}
|
|
270
273
|
function toOne(target, options) {
|
|
271
274
|
return {
|
|
272
275
|
kind: LINK_BRAND,
|
|
@@ -320,51 +323,51 @@ function assertIdentifier({ value, what, file, pattern = IDENTIFIER_PATTERN }) {
|
|
|
320
323
|
);
|
|
321
324
|
}
|
|
322
325
|
}
|
|
323
|
-
function defineTable(
|
|
326
|
+
function defineTable(name2, columns, config = {}) {
|
|
324
327
|
const file = activeAuthoringContext()?.currentFile;
|
|
325
|
-
assertIdentifier({ value:
|
|
328
|
+
assertIdentifier({ value: name2, what: "table name", file });
|
|
326
329
|
const entries = Object.entries(columns);
|
|
327
330
|
if (entries.length === 0) {
|
|
328
|
-
throw new OrmDefinitionError(`table "${
|
|
331
|
+
throw new OrmDefinitionError(`table "${name2}" declares no columns`, file);
|
|
329
332
|
}
|
|
330
333
|
const seenColumnNames = /* @__PURE__ */ new Map();
|
|
331
334
|
for (const [propertyName, builder] of entries) {
|
|
332
335
|
if (!isColumnBuilder(builder)) {
|
|
333
336
|
throw new OrmDefinitionError(
|
|
334
|
-
`table "${
|
|
337
|
+
`table "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
|
|
335
338
|
file
|
|
336
339
|
);
|
|
337
340
|
}
|
|
338
341
|
const columnName = builder.columnNameFor(propertyName);
|
|
339
|
-
assertIdentifier({ value: columnName, what: `table "${
|
|
342
|
+
assertIdentifier({ value: columnName, what: `table "${name2}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
|
|
340
343
|
if (config.materializes === void 0 && (propertyName === CELLS_FIELD || Object.values(CELL_PART_SUFFIX).some((suffix) => columnName.endsWith(suffix) || propertyName.endsWith(suffix)))) {
|
|
341
344
|
throw new OrmDefinitionError(
|
|
342
|
-
`table "${
|
|
345
|
+
`table "${name2}": "${propertyName}" collides with the reserved cell-metadata names ('${CELLS_FIELD}', or a ${Object.values(CELL_PART_SUFFIX).join("/")} suffix)`,
|
|
343
346
|
file
|
|
344
347
|
);
|
|
345
348
|
}
|
|
346
349
|
const existing = seenColumnNames.get(columnName);
|
|
347
350
|
if (existing !== void 0) {
|
|
348
351
|
throw new OrmDefinitionError(
|
|
349
|
-
`table "${
|
|
352
|
+
`table "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
|
|
350
353
|
file
|
|
351
354
|
);
|
|
352
355
|
}
|
|
353
356
|
seenColumnNames.set(columnName, propertyName);
|
|
354
357
|
}
|
|
355
|
-
const table = { kind: TABLE_BRAND, tableName:
|
|
358
|
+
const table = { kind: TABLE_BRAND, tableName: name2, columns, config, source: void 0, file };
|
|
356
359
|
registerTable(table);
|
|
357
360
|
return table;
|
|
358
361
|
}
|
|
359
|
-
function defineSyncedTable(
|
|
362
|
+
function defineSyncedTable(name2, columnsOrConfig, maybeConfig) {
|
|
360
363
|
const file = activeAuthoringContext()?.currentFile;
|
|
361
|
-
assertIdentifier({ value:
|
|
364
|
+
assertIdentifier({ value: name2, what: "table name", file });
|
|
362
365
|
const isTyped = maybeConfig !== void 0;
|
|
363
366
|
const columns = isTyped ? columnsOrConfig : {};
|
|
364
367
|
const config = isTyped ? maybeConfig : columnsOrConfig;
|
|
365
368
|
if (!SYNCED_SOURCE_PATTERN.test(config.source)) {
|
|
366
369
|
throw new OrmDefinitionError(
|
|
367
|
-
`synced table "${
|
|
370
|
+
`synced table "${name2}": source "${config.source}" must be a qualified lowercase schema.table identifier`,
|
|
368
371
|
file
|
|
369
372
|
);
|
|
370
373
|
}
|
|
@@ -374,21 +377,21 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
|
|
|
374
377
|
for (const [propertyName, builder] of entries) {
|
|
375
378
|
if (!isColumnBuilder(builder)) {
|
|
376
379
|
throw new OrmDefinitionError(
|
|
377
|
-
`synced table "${
|
|
380
|
+
`synced table "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
|
|
378
381
|
file
|
|
379
382
|
);
|
|
380
383
|
}
|
|
381
384
|
const columnName = builder.columnNameFor(propertyName);
|
|
382
385
|
assertIdentifier({
|
|
383
386
|
value: columnName,
|
|
384
|
-
what: `synced table "${
|
|
387
|
+
what: `synced table "${name2}": column "${propertyName}" column name`,
|
|
385
388
|
file,
|
|
386
389
|
pattern: COLUMN_IDENTIFIER_PATTERN
|
|
387
390
|
});
|
|
388
391
|
const existing = seenColumnNames.get(columnName);
|
|
389
392
|
if (existing !== void 0) {
|
|
390
393
|
throw new OrmDefinitionError(
|
|
391
|
-
`synced table "${
|
|
394
|
+
`synced table "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
|
|
392
395
|
file
|
|
393
396
|
);
|
|
394
397
|
}
|
|
@@ -399,13 +402,13 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
|
|
|
399
402
|
const hasConfigPk = config.primaryKey !== void 0 && config.primaryKey.length > 0;
|
|
400
403
|
if (columnLevelPk.length === 0 && !hasConfigPk) {
|
|
401
404
|
throw new OrmDefinitionError(
|
|
402
|
-
`synced table "${
|
|
405
|
+
`synced table "${name2}" declares columns but no primary key \u2014 use .primaryKey() or config.primaryKey`,
|
|
403
406
|
file
|
|
404
407
|
);
|
|
405
408
|
}
|
|
406
409
|
if (columnLevelPk.length > 0 && hasConfigPk) {
|
|
407
410
|
throw new OrmDefinitionError(
|
|
408
|
-
`synced table "${
|
|
411
|
+
`synced table "${name2}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
|
|
409
412
|
file
|
|
410
413
|
);
|
|
411
414
|
}
|
|
@@ -417,7 +420,7 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
|
|
|
417
420
|
if (config.policies !== void 0) tableConfig.policies = { read: config.policies.read };
|
|
418
421
|
const table = {
|
|
419
422
|
kind: TABLE_BRAND,
|
|
420
|
-
tableName:
|
|
423
|
+
tableName: name2,
|
|
421
424
|
columns,
|
|
422
425
|
config: tableConfig,
|
|
423
426
|
source: config.source,
|
|
@@ -426,26 +429,26 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
|
|
|
426
429
|
registerTable(table);
|
|
427
430
|
return table;
|
|
428
431
|
}
|
|
429
|
-
function defineEnum(
|
|
432
|
+
function defineEnum(name2, values) {
|
|
430
433
|
const file = activeAuthoringContext()?.currentFile;
|
|
431
|
-
assertIdentifier({ value:
|
|
434
|
+
assertIdentifier({ value: name2, what: "enum name", file });
|
|
432
435
|
if (values.length === 0 || values.length > 100) {
|
|
433
|
-
throw new OrmDefinitionError(`enum "${
|
|
436
|
+
throw new OrmDefinitionError(`enum "${name2}" must declare between 1 and 100 values`, file);
|
|
434
437
|
}
|
|
435
438
|
const seen = /* @__PURE__ */ new Set();
|
|
436
439
|
for (const value of values) {
|
|
437
440
|
if (value.length === 0 || value.length > MAX_IDENTIFIER_LENGTH) {
|
|
438
441
|
throw new OrmDefinitionError(
|
|
439
|
-
`enum "${
|
|
442
|
+
`enum "${name2}": value ${JSON.stringify(value)} must be 1-${MAX_IDENTIFIER_LENGTH} characters`,
|
|
440
443
|
file
|
|
441
444
|
);
|
|
442
445
|
}
|
|
443
446
|
if (seen.has(value)) {
|
|
444
|
-
throw new OrmDefinitionError(`enum "${
|
|
447
|
+
throw new OrmDefinitionError(`enum "${name2}" declares duplicate value "${value}"`, file);
|
|
445
448
|
}
|
|
446
449
|
seen.add(value);
|
|
447
450
|
}
|
|
448
|
-
const ref = { kind: ENUM_BRAND, name, values: [...values] };
|
|
451
|
+
const ref = { kind: ENUM_BRAND, name: name2, values: [...values] };
|
|
449
452
|
registerEnum(ref);
|
|
450
453
|
return ref;
|
|
451
454
|
}
|
|
@@ -453,52 +456,52 @@ var VIEW_BRAND = "seq-view";
|
|
|
453
456
|
function isSeqView(value) {
|
|
454
457
|
return typeof value === "object" && value !== null && value.kind === VIEW_BRAND;
|
|
455
458
|
}
|
|
456
|
-
function defineView(
|
|
459
|
+
function defineView(name2, columns, config) {
|
|
457
460
|
const file = activeAuthoringContext()?.currentFile;
|
|
458
|
-
if (!IDENTIFIER_PATTERN.test(
|
|
461
|
+
if (!IDENTIFIER_PATTERN.test(name2) || name2.length > MAX_IDENTIFIER_LENGTH) {
|
|
459
462
|
throw new OrmDefinitionError(
|
|
460
|
-
`view name "${
|
|
463
|
+
`view name "${name2}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
|
|
461
464
|
file
|
|
462
465
|
);
|
|
463
466
|
}
|
|
464
467
|
const entries = Object.entries(columns);
|
|
465
|
-
if (entries.length === 0) throw new OrmDefinitionError(`view "${
|
|
468
|
+
if (entries.length === 0) throw new OrmDefinitionError(`view "${name2}" declares no columns`, file);
|
|
466
469
|
const seenColumnNames = /* @__PURE__ */ new Map();
|
|
467
470
|
for (const [propertyName, builder] of entries) {
|
|
468
471
|
if (!isColumnBuilder(builder)) {
|
|
469
472
|
throw new OrmDefinitionError(
|
|
470
|
-
`view "${
|
|
473
|
+
`view "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories`,
|
|
471
474
|
file
|
|
472
475
|
);
|
|
473
476
|
}
|
|
474
477
|
const columnName = builder.columnNameFor(propertyName);
|
|
475
478
|
if (!COLUMN_IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
|
|
476
|
-
throw new OrmDefinitionError(`view "${
|
|
479
|
+
throw new OrmDefinitionError(`view "${name2}": column "${propertyName}" has invalid column name "${columnName}"`, file);
|
|
477
480
|
}
|
|
478
481
|
const existing = seenColumnNames.get(columnName);
|
|
479
482
|
if (existing !== void 0) {
|
|
480
483
|
throw new OrmDefinitionError(
|
|
481
|
-
`view "${
|
|
484
|
+
`view "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
|
|
482
485
|
file
|
|
483
486
|
);
|
|
484
487
|
}
|
|
485
488
|
seenColumnNames.set(columnName, propertyName);
|
|
486
489
|
}
|
|
487
|
-
const view = { kind: VIEW_BRAND, viewName:
|
|
490
|
+
const view = { kind: VIEW_BRAND, viewName: name2, columns, config, file };
|
|
488
491
|
registerView(view);
|
|
489
492
|
return view;
|
|
490
493
|
}
|
|
491
494
|
var QUERY_COLUMN_FACTORIES = {
|
|
492
|
-
uuid: (
|
|
493
|
-
text: (
|
|
494
|
-
integer: (
|
|
495
|
-
bigint: (
|
|
496
|
-
numeric: (
|
|
497
|
-
double: (
|
|
498
|
-
boolean: (
|
|
499
|
-
timestamptz: (
|
|
500
|
-
date: (
|
|
501
|
-
jsonb: (
|
|
495
|
+
uuid: (name2) => pgCore.uuid(name2),
|
|
496
|
+
text: (name2) => pgCore.text(name2),
|
|
497
|
+
integer: (name2) => pgCore.integer(name2),
|
|
498
|
+
bigint: (name2) => pgCore.bigint(name2, { mode: "bigint" }),
|
|
499
|
+
numeric: (name2) => pgCore.numeric(name2),
|
|
500
|
+
double: (name2) => pgCore.doublePrecision(name2),
|
|
501
|
+
boolean: (name2) => pgCore.boolean(name2),
|
|
502
|
+
timestamptz: (name2) => pgCore.timestamp(name2, { withTimezone: true }),
|
|
503
|
+
date: (name2) => pgCore.date(name2),
|
|
504
|
+
jsonb: (name2) => pgCore.jsonb(name2)
|
|
502
505
|
};
|
|
503
506
|
function compileViewQueries(input) {
|
|
504
507
|
const schemaName = `orm_${input.namespace}`;
|
|
@@ -554,9 +557,9 @@ function compileViewQueries(input) {
|
|
|
554
557
|
}
|
|
555
558
|
const handle = handles.get(resolved);
|
|
556
559
|
if (!handle) {
|
|
557
|
-
const
|
|
560
|
+
const name2 = isSeqTable(resolved) ? resolved.tableName : resolved.viewName;
|
|
558
561
|
throw new OrmDefinitionError(
|
|
559
|
-
`view "${authored.viewName}" references relation "${
|
|
562
|
+
`view "${authored.viewName}" references relation "${name2}" which is not part of this namespace`,
|
|
560
563
|
authored.file
|
|
561
564
|
);
|
|
562
565
|
}
|
|
@@ -747,8 +750,8 @@ function overlayWinsUnion(input) {
|
|
|
747
750
|
// src/authoring/cow-mutations.ts
|
|
748
751
|
function overrideRowMutation(input) {
|
|
749
752
|
const { overlay, synced, pk } = input;
|
|
750
|
-
const
|
|
751
|
-
assertMutationName(
|
|
753
|
+
const name2 = input.name ?? `override_${overlay.tableName}`;
|
|
754
|
+
assertMutationName(name2, "overrideRowMutation");
|
|
752
755
|
assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "overrideRowMutation" });
|
|
753
756
|
const sharedProperties = sharedNonPkProperties({ overlay, synced, pk });
|
|
754
757
|
const setProperties = resolveSetProperties({
|
|
@@ -767,7 +770,7 @@ function overrideRowMutation(input) {
|
|
|
767
770
|
});
|
|
768
771
|
return {
|
|
769
772
|
kind: "override",
|
|
770
|
-
name,
|
|
773
|
+
name: name2,
|
|
771
774
|
synced: synced.tableName,
|
|
772
775
|
pk,
|
|
773
776
|
copy: [pk, ...sharedProperties],
|
|
@@ -776,23 +779,23 @@ function overrideRowMutation(input) {
|
|
|
776
779
|
}
|
|
777
780
|
function revertOverlayMutation(input) {
|
|
778
781
|
const { overlay, synced, pk } = input;
|
|
779
|
-
const
|
|
780
|
-
assertMutationName(
|
|
782
|
+
const name2 = input.name ?? `revert_${overlay.tableName}`;
|
|
783
|
+
assertMutationName(name2, "revertOverlayMutation");
|
|
781
784
|
assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "revertOverlayMutation" });
|
|
782
785
|
assertMatchingColumns({ overlay, synced, properties: [pk], helper: "revertOverlayMutation" });
|
|
783
786
|
return {
|
|
784
787
|
kind: "revert",
|
|
785
|
-
name,
|
|
788
|
+
name: name2,
|
|
786
789
|
synced: synced.tableName,
|
|
787
790
|
pk,
|
|
788
791
|
copy: [],
|
|
789
792
|
set: []
|
|
790
793
|
};
|
|
791
794
|
}
|
|
792
|
-
function assertMutationName(
|
|
793
|
-
if (!IDENTIFIER_PATTERN.test(
|
|
795
|
+
function assertMutationName(name2, helper) {
|
|
796
|
+
if (!IDENTIFIER_PATTERN.test(name2) || name2.length > MAX_IDENTIFIER_LENGTH) {
|
|
794
797
|
throw new OrmDefinitionError(
|
|
795
|
-
`${helper}: mutation name "${
|
|
798
|
+
`${helper}: mutation name "${name2}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`
|
|
796
799
|
);
|
|
797
800
|
}
|
|
798
801
|
}
|
|
@@ -918,17 +921,35 @@ function scalarColumnType(table, property, helper) {
|
|
|
918
921
|
}
|
|
919
922
|
return type;
|
|
920
923
|
}
|
|
924
|
+
var objectRef2 = zod.z.string().max(255).regex(/^[a-z][a-z0-9_]{0,40}\/[A-Za-z_][A-Za-z0-9_]*$/, "Expected an explicit namespace/object reference (no wildcards)");
|
|
925
|
+
var functionRef = zod.z.string().max(255).regex(/^[a-z][a-z0-9-]*$/, "Expected a function slug");
|
|
926
|
+
var refs = (schema) => zod.z.array(schema).max(256).refine(
|
|
927
|
+
(values) => new Set(values).size === values.length,
|
|
928
|
+
"Duplicate dependency reference"
|
|
929
|
+
).default([]);
|
|
930
|
+
var ontologyContractSchema = zod.z.strictObject({
|
|
931
|
+
version: zod.z.literal(1),
|
|
932
|
+
read: refs(objectRef2),
|
|
933
|
+
write: refs(objectRef2),
|
|
934
|
+
produce: refs(objectRef2),
|
|
935
|
+
invoke: refs(functionRef)
|
|
936
|
+
}).refine((contract) => JSON.stringify(contract).length <= 4096, "Ontology contract exceeds the 4 KiB signed-context budget");
|
|
937
|
+
function readOntologyContract(manifest) {
|
|
938
|
+
if (!manifest || typeof manifest !== "object" || !("ontology" in manifest) || manifest.ontology === void 0) return void 0;
|
|
939
|
+
return ontologyContractSchema.parse(manifest.ontology);
|
|
940
|
+
}
|
|
921
941
|
|
|
922
942
|
// src/compiler/sql.ts
|
|
923
943
|
var CLAIMS_SETTING = "request.jwt.claims";
|
|
924
944
|
var CAPABILITIES_CLAIM_KEY = "capabilities";
|
|
925
945
|
var ROLES_CLAIM_KEY = "roles";
|
|
926
|
-
var quoteIdentifier = (
|
|
946
|
+
var quoteIdentifier = (name2) => `"${name2.replaceAll('"', '""')}"`;
|
|
927
947
|
var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
928
948
|
var CLAIMS_SOURCE = `nullif(current_setting('${CLAIMS_SETTING}', true), '')::jsonb`;
|
|
929
949
|
var claimExpression = (key) => `(${CLAIMS_SOURCE} ->> ${quoteLiteral(key)})`;
|
|
930
|
-
var
|
|
931
|
-
var
|
|
950
|
+
var scopedClaimsSource = (namespace) => namespace ? `(CASE WHEN ${CLAIMS_SOURCE} ? 'namespaces' THEN ${CLAIMS_SOURCE} -> 'namespaces' -> ${quoteLiteral(namespace)} ELSE ${CLAIMS_SOURCE} END)` : CLAIMS_SOURCE;
|
|
951
|
+
var capabilityExpression = (key, namespace) => `coalesce(${scopedClaimsSource(namespace)} -> '${CAPABILITIES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
|
|
952
|
+
var roleExpression = (key, namespace) => `coalesce(${scopedClaimsSource(namespace)} -> '${ROLES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
|
|
932
953
|
|
|
933
954
|
// src/checks/compile.ts
|
|
934
955
|
function tokenize(source) {
|
|
@@ -1261,8 +1282,8 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
1261
1282
|
validateRules(scope);
|
|
1262
1283
|
validateViewCells(scope);
|
|
1263
1284
|
if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
|
|
1264
|
-
for (const
|
|
1265
|
-
if (scope.sourceByName.get(
|
|
1285
|
+
for (const name2 of resolved.deletedBy) {
|
|
1286
|
+
if (scope.sourceByName.get(name2)?.kind !== "manual") fail(`deletedBy '${name2}' must be the manual source`);
|
|
1266
1287
|
}
|
|
1267
1288
|
return errors;
|
|
1268
1289
|
}
|
|
@@ -1359,7 +1380,7 @@ function validateViewCells({ view, dataColumns, pkColumn, fail }) {
|
|
|
1359
1380
|
if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
|
|
1360
1381
|
expected.delete(column.name);
|
|
1361
1382
|
}
|
|
1362
|
-
for (const
|
|
1383
|
+
for (const name2 of expected.keys()) fail(`missing cell column '${name2}'`);
|
|
1363
1384
|
}
|
|
1364
1385
|
var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
|
|
1365
1386
|
function validateMaterialized({ scope, tableByName, views }) {
|
|
@@ -1450,6 +1471,8 @@ var canonicalLinkSchema = zod.z.object({
|
|
|
1450
1471
|
kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
|
|
1451
1472
|
/** Target table name (namespace-local). Absent for toPlatformUser. */
|
|
1452
1473
|
target: identifier2.optional(),
|
|
1474
|
+
/** Owning namespace of an imported target; omitted for local links. */
|
|
1475
|
+
namespace: identifier2.max(41).optional(),
|
|
1453
1476
|
/** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
|
|
1454
1477
|
on: columnIdentifier,
|
|
1455
1478
|
/** Referenced column on the target (default: target's PK / platform user `sub`). */
|
|
@@ -1708,11 +1731,12 @@ var canonicalForeignKeySchema = zod.z.object({
|
|
|
1708
1731
|
}).strict();
|
|
1709
1732
|
var canonicalTableSchema = zod.z.object({
|
|
1710
1733
|
table: identifier2,
|
|
1734
|
+
displayName: zod.z.string().trim().min(1).max(120).optional(),
|
|
1711
1735
|
description: zod.z.string().max(2e3).optional(),
|
|
1712
1736
|
/** Table-level rename hint (new in v2). */
|
|
1713
1737
|
renamedFrom: identifier2.optional(),
|
|
1714
|
-
/** Synced/reflected read-only tables (`source:
|
|
1715
|
-
source: zod.z.string().regex(
|
|
1738
|
+
/** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
|
|
1739
|
+
source: zod.z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
|
|
1716
1740
|
columns: zod.z.array(canonicalColumnSchema).default([]),
|
|
1717
1741
|
primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
|
|
1718
1742
|
links: zod.z.array(canonicalLinkSchema).default([]),
|
|
@@ -1865,7 +1889,9 @@ function validateCellMetadata(table, index3, ctx) {
|
|
|
1865
1889
|
}
|
|
1866
1890
|
}
|
|
1867
1891
|
}
|
|
1868
|
-
var
|
|
1892
|
+
var exportGateSchema = zod.z.union([zod.z.literal(true), zod.z.object({ capability: zod.z.string().min(1) }).strict(), zod.z.object({ role: zod.z.string().min(1) }).strict()]);
|
|
1893
|
+
var relationExportSchema = zod.z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
|
|
1894
|
+
var localDefinitionSchema = zod.z.object({
|
|
1869
1895
|
definition_format: zod.z.literal(CANONICAL_FORMAT),
|
|
1870
1896
|
namespace: identifier2.max(41),
|
|
1871
1897
|
description: zod.z.string().max(2e3).optional(),
|
|
@@ -1873,6 +1899,8 @@ var canonicalDefinitionSchema = zod.z.object({
|
|
|
1873
1899
|
tables: zod.z.array(canonicalTableSchema).min(0).max(500),
|
|
1874
1900
|
/** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
|
|
1875
1901
|
views: zod.z.array(canonicalViewSchema).max(500).optional(),
|
|
1902
|
+
/** Target-owned cross-namespace access contract, using existing namespace grants. */
|
|
1903
|
+
exports: zod.z.record(identifier2, relationExportSchema).optional(),
|
|
1876
1904
|
/** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
|
|
1877
1905
|
capabilities: zod.z.object({
|
|
1878
1906
|
roles: zod.z.unknown().optional(),
|
|
@@ -1899,7 +1927,16 @@ var canonicalDefinitionSchema = zod.z.object({
|
|
|
1899
1927
|
}
|
|
1900
1928
|
}
|
|
1901
1929
|
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1930
|
+
const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
|
|
1931
|
+
for (const name2 of Object.keys(definition.exports ?? {})) {
|
|
1932
|
+
if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
|
|
1933
|
+
}
|
|
1902
1934
|
for (const [tableIndex, table] of definition.tables.entries()) {
|
|
1935
|
+
for (const [linkIndex, link] of table.links.entries()) {
|
|
1936
|
+
if (link.enforce && link.namespace !== void 0 && link.namespace !== definition.namespace) {
|
|
1937
|
+
ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", linkIndex], message: "physical foreign keys require a local target; use an ORM link without enforce" });
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1903
1940
|
const names = /* @__PURE__ */ new Set([
|
|
1904
1941
|
...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
|
|
1905
1942
|
...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
|
|
@@ -1936,14 +1973,14 @@ var canonicalDefinitionSchema = zod.z.object({
|
|
|
1936
1973
|
}
|
|
1937
1974
|
if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
|
|
1938
1975
|
if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
|
|
1939
|
-
for (const [position,
|
|
1940
|
-
const sourceColumn = table.columns.find((column) => column.columnName ===
|
|
1976
|
+
for (const [position, name2] of fk.columns.entries()) {
|
|
1977
|
+
const sourceColumn = table.columns.find((column) => column.columnName === name2);
|
|
1941
1978
|
const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
|
|
1942
1979
|
if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
|
|
1943
1980
|
else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
|
|
1944
1981
|
}
|
|
1945
1982
|
if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
|
|
1946
|
-
if (fk.columns.some((
|
|
1983
|
+
if (fk.columns.some((name2) => table.columns.find((column) => column.columnName === name2)?.nullable === false || table.primaryKey?.includes(name2))) fail("SET NULL requires nullable source columns");
|
|
1947
1984
|
}
|
|
1948
1985
|
const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index4) => index4.unique).map((index4) => index4.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
|
|
1949
1986
|
if (!uniqueKeys.some((key) => key.length === fk.references.length && key.every((column) => fk.references.includes(column)))) fail("foreign key target columns must form a unique key");
|
|
@@ -2048,27 +2085,31 @@ var canonicalDefinitionSchema = zod.z.object({
|
|
|
2048
2085
|
const visiting = /* @__PURE__ */ new Set();
|
|
2049
2086
|
const visited = /* @__PURE__ */ new Set();
|
|
2050
2087
|
let cycleReported = false;
|
|
2051
|
-
const visit = (
|
|
2052
|
-
if (visited.has(
|
|
2053
|
-
if (visiting.has(
|
|
2088
|
+
const visit = (name2, path) => {
|
|
2089
|
+
if (visited.has(name2) || cycleReported) return;
|
|
2090
|
+
if (visiting.has(name2)) {
|
|
2054
2091
|
cycleReported = true;
|
|
2055
2092
|
ctx.addIssue({
|
|
2056
2093
|
code: "custom",
|
|
2057
2094
|
path: ["views"],
|
|
2058
|
-
message: `view dependencies form a cycle: ${[...path,
|
|
2095
|
+
message: `view dependencies form a cycle: ${[...path, name2].join(" -> ")}`
|
|
2059
2096
|
});
|
|
2060
2097
|
return;
|
|
2061
2098
|
}
|
|
2062
|
-
visiting.add(
|
|
2063
|
-
const view = viewByName.get(
|
|
2099
|
+
visiting.add(name2);
|
|
2100
|
+
const view = viewByName.get(name2);
|
|
2064
2101
|
for (const dependency of view?.dependencies ?? []) {
|
|
2065
|
-
if (viewByName.has(dependency)) visit(dependency, [...path,
|
|
2102
|
+
if (viewByName.has(dependency)) visit(dependency, [...path, name2]);
|
|
2066
2103
|
}
|
|
2067
|
-
visiting.delete(
|
|
2068
|
-
visited.add(
|
|
2104
|
+
visiting.delete(name2);
|
|
2105
|
+
visited.add(name2);
|
|
2069
2106
|
};
|
|
2070
2107
|
for (const view of views) visit(view.view, []);
|
|
2071
2108
|
});
|
|
2109
|
+
var canonicalDefinitionSchema = localDefinitionSchema.safeExtend({
|
|
2110
|
+
/** Flattened, pinned snapshots. Imported relations never participate in this namespace's DDL. */
|
|
2111
|
+
imports: zod.z.array(localDefinitionSchema).max(32).optional()
|
|
2112
|
+
});
|
|
2072
2113
|
function canonicalRelations(definition) {
|
|
2073
2114
|
const views = (definition.views ?? []).map((view) => ({
|
|
2074
2115
|
table: view.view,
|
|
@@ -2112,6 +2153,11 @@ function normalizeCanonical(definition) {
|
|
|
2112
2153
|
dependencies: [...view.dependencies].sort()
|
|
2113
2154
|
}));
|
|
2114
2155
|
}
|
|
2156
|
+
if (definition.imports?.length) {
|
|
2157
|
+
normalized.imports = definition.imports.map(normalizeCanonical).sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
2158
|
+
} else {
|
|
2159
|
+
delete normalized.imports;
|
|
2160
|
+
}
|
|
2115
2161
|
return normalized;
|
|
2116
2162
|
}
|
|
2117
2163
|
|
|
@@ -2236,8 +2282,8 @@ var literal = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
|
2236
2282
|
var physical = ({ source, property }) => source.table.columns[property].columnNameFor(property);
|
|
2237
2283
|
function candidateSql({ source, column, pk, pgType }) {
|
|
2238
2284
|
const a = ident(`s_${source.name}`);
|
|
2239
|
-
const
|
|
2240
|
-
const value = `CAST(${a}.${ident(
|
|
2285
|
+
const name2 = physical({ source, property: column });
|
|
2286
|
+
const value = `CAST(${a}.${ident(name2)} AS ${pgType})`;
|
|
2241
2287
|
if (source.kind === "etl") {
|
|
2242
2288
|
return {
|
|
2243
2289
|
source: source.name,
|
|
@@ -2247,9 +2293,9 @@ function candidateSql({ source, column, pk, pgType }) {
|
|
|
2247
2293
|
by: "CAST(NULL AS text)"
|
|
2248
2294
|
};
|
|
2249
2295
|
}
|
|
2250
|
-
const at = `${a}.${ident(cellColumnName(
|
|
2296
|
+
const at = `${a}.${ident(cellColumnName(name2, "at"))}`;
|
|
2251
2297
|
const tombstoned2 = source.kind === "prefetch" ? ` AND NOT ${a}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName)}` : "";
|
|
2252
|
-
return { source: source.name, value, has: `(${at} IS NOT NULL${tombstoned2})`, at, by: `${a}.${ident(cellColumnName(
|
|
2298
|
+
return { source: source.name, value, has: `(${at} IS NOT NULL${tombstoned2})`, at, by: `${a}.${ident(cellColumnName(name2, "by"))}` };
|
|
2253
2299
|
}
|
|
2254
2300
|
function outranks({ i, j, key, direction, inclusive }) {
|
|
2255
2301
|
const ki = i[key];
|
|
@@ -2342,7 +2388,7 @@ function buildResolvedQuery({ context, plan }) {
|
|
|
2342
2388
|
plan.rules.forEach((rule, index3) => {
|
|
2343
2389
|
const canonical = plan.columns[rule.column].toCanonicalColumn(rule.column);
|
|
2344
2390
|
const pgType = typeof canonical.type === "string" ? PG_TYPE[canonical.type] : `${schema === void 0 ? "" : `${ident(schema)}.`}${ident(canonical.type.enum)}`;
|
|
2345
|
-
const candidates = rule.candidates.map((
|
|
2391
|
+
const candidates = rule.candidates.map((name2) => candidateSql({ source: sourceByName.get(name2), column: rule.column, pk: plan.pk, pgType }));
|
|
2346
2392
|
cells.set(rule.column, rule.comparator === "custom" ? customCell({ candidates, orderBy: rule.orderBy, index: index3 }) : builtinCell({ comparator: rule.comparator, candidates }));
|
|
2347
2393
|
});
|
|
2348
2394
|
const laterals = [...cells.values()].flatMap((cell) => cell.lateral ? [drizzleOrm.sql.raw(cell.lateral)] : []);
|
|
@@ -2363,7 +2409,7 @@ function buildResolvedQuery({ context, plan }) {
|
|
|
2363
2409
|
const system = RESOLVED_VIEW_SYSTEM_COLUMNS;
|
|
2364
2410
|
selected[system.writebackStatus.name] = drizzleOrm.sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.columnName)}`).as(system.writebackStatus.columnName);
|
|
2365
2411
|
selected[system.writebackError.name] = drizzleOrm.sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.columnName)}`).as(system.writebackError.columnName);
|
|
2366
|
-
const hidden = plan.deletedBy.map((
|
|
2412
|
+
const hidden = plan.deletedBy.map((name2) => `NOT COALESCE(${alias(name2)}.${ident(tombstoned2)}, false)`);
|
|
2367
2413
|
const query = qb.select(selected).from(from);
|
|
2368
2414
|
return hidden.length > 0 ? query.where(drizzleOrm.sql.raw(hidden.join(" AND "))) : query;
|
|
2369
2415
|
}
|
|
@@ -2389,12 +2435,12 @@ var SYSTEM_VIEW_COLUMNS = {
|
|
|
2389
2435
|
[RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.columnName),
|
|
2390
2436
|
[RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.columnName)
|
|
2391
2437
|
};
|
|
2392
|
-
function defineResolvedTable(
|
|
2438
|
+
function defineResolvedTable(name2, columns, config) {
|
|
2393
2439
|
const file = activeAuthoringContext()?.currentFile;
|
|
2394
2440
|
const fail = (message) => {
|
|
2395
|
-
throw new OrmDefinitionError(`resolved table "${
|
|
2441
|
+
throw new OrmDefinitionError(`resolved table "${name2}": ${message}`, file);
|
|
2396
2442
|
};
|
|
2397
|
-
if (!IDENTIFIER_PATTERN.test(
|
|
2443
|
+
if (!IDENTIFIER_PATTERN.test(name2)) fail(`name must match ${IDENTIFIER_PATTERN}`);
|
|
2398
2444
|
for (const property of Object.keys(columns)) {
|
|
2399
2445
|
if (RESERVED_RESOLVED_COLUMN_NAMES.has(property) || RESERVED_RESOLVED_COLUMN_NAMES.has(columns[property].columnNameFor(property))) {
|
|
2400
2446
|
fail(`column "${property}" collides with a reserved system column`);
|
|
@@ -2413,7 +2459,7 @@ function defineResolvedTable(name, columns, config) {
|
|
|
2413
2459
|
const sourceEntries = Object.entries(config.sources);
|
|
2414
2460
|
if (sourceEntries.filter(([, source]) => source.kind === "manual").length !== 1) fail("declare exactly one manualSource()");
|
|
2415
2461
|
const plans = sourceEntries.map(
|
|
2416
|
-
([sourceName, input]) => planSource({ viewName:
|
|
2462
|
+
([sourceName, input]) => planSource({ viewName: name2, sourceName, input, columns, pk, pkBuilder, dataColumns, fail })
|
|
2417
2463
|
);
|
|
2418
2464
|
const rules = planRules({ config, plans, dataColumns, fail });
|
|
2419
2465
|
const deletedBy = [...config.rows?.deletedBy ?? []];
|
|
@@ -2447,23 +2493,23 @@ function defineResolvedTable(name, columns, config) {
|
|
|
2447
2493
|
policies: { read }
|
|
2448
2494
|
};
|
|
2449
2495
|
if (!config.materialize) {
|
|
2450
|
-
return defineView(
|
|
2496
|
+
return defineView(name2, typedColumns, { ...surface, query: (context) => buildResolvedQuery({ context, plan: queryPlan }), resolved });
|
|
2451
2497
|
}
|
|
2452
2498
|
const materialize = config.materialize === true ? {} : config.materialize;
|
|
2453
|
-
const computeName = `${
|
|
2454
|
-
const tableName = `${
|
|
2499
|
+
const computeName = `${name2}__compute`;
|
|
2500
|
+
const tableName = `${name2}__materialized`;
|
|
2455
2501
|
const compute = defineView(computeName, typedColumns, {
|
|
2456
2502
|
policies: { read },
|
|
2457
2503
|
query: (context) => buildResolvedQuery({ context, plan: queryPlan }),
|
|
2458
2504
|
internal: true
|
|
2459
2505
|
});
|
|
2460
2506
|
const table = defineTable(tableName, viewColumns, {
|
|
2461
|
-
description: `Materialized rows of resolved table "${
|
|
2507
|
+
description: `Materialized rows of resolved table "${name2}". System-maintained from "${computeName}".`,
|
|
2462
2508
|
internal: true,
|
|
2463
|
-
materializes:
|
|
2509
|
+
materializes: name2,
|
|
2464
2510
|
...materialize.indexes !== void 0 ? { indexes: materialize.indexes } : {}
|
|
2465
2511
|
});
|
|
2466
|
-
return defineView(
|
|
2512
|
+
return defineView(name2, typedColumns, {
|
|
2467
2513
|
...surface,
|
|
2468
2514
|
query: (context) => {
|
|
2469
2515
|
const handle = context.relation(table);
|
|
@@ -2490,7 +2536,7 @@ function planRules({ config, plans, dataColumns, fail }) {
|
|
|
2490
2536
|
return dataColumns.map((column) => {
|
|
2491
2537
|
const explicit = config.resolve?.[column];
|
|
2492
2538
|
const comparator = explicit ?? fallback;
|
|
2493
|
-
const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((
|
|
2539
|
+
const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((name2) => plans.find((plan) => plan.name === name2)?.columns.includes(column));
|
|
2494
2540
|
for (const candidate of candidates) {
|
|
2495
2541
|
const plan = plans.find((entry) => entry.name === candidate);
|
|
2496
2542
|
if (!plan) fail(`column "${column}" names unknown source "${candidate}"`);
|
|
@@ -2592,8 +2638,8 @@ function canonicalTable(table) {
|
|
|
2592
2638
|
},
|
|
2593
2639
|
actions: emptyToNull(
|
|
2594
2640
|
Object.fromEntries(
|
|
2595
|
-
Object.entries(table.actions ?? {}).map(([
|
|
2596
|
-
|
|
2641
|
+
Object.entries(table.actions ?? {}).map(([name2, action]) => [
|
|
2642
|
+
name2,
|
|
2597
2643
|
{
|
|
2598
2644
|
description: action.description ?? null,
|
|
2599
2645
|
requires: action.requires?.source ?? null,
|
|
@@ -2647,6 +2693,72 @@ function computeDefinitionHash(definition) {
|
|
|
2647
2693
|
return crypto.createHash("sha256").update(HASH_DOMAIN).update(stableJson(canonicalizeDefinition(definition))).digest("hex");
|
|
2648
2694
|
}
|
|
2649
2695
|
|
|
2696
|
+
// src/canonical/composition.ts
|
|
2697
|
+
function localContract(definition) {
|
|
2698
|
+
return normalizeCanonical(localDefinitionSchema.parse(definition));
|
|
2699
|
+
}
|
|
2700
|
+
function flattenNamespaceImports(inputs) {
|
|
2701
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
2702
|
+
const visit = (value, depth) => {
|
|
2703
|
+
if (depth > 16 || definitions.size > 32) throw new OrmDefinitionError("namespace import graph is too large");
|
|
2704
|
+
const definition = localContract(localDefinitionSchema.parse(value));
|
|
2705
|
+
const previous = definitions.get(definition.namespace);
|
|
2706
|
+
if (previous) {
|
|
2707
|
+
if (stableJson(previous) !== stableJson(definition)) throw new OrmDefinitionError(`conflicting contracts for namespace '${definition.namespace}'`);
|
|
2708
|
+
}
|
|
2709
|
+
definitions.set(definition.namespace, definition);
|
|
2710
|
+
if (typeof value === "object" && value !== null && "imports" in value && Array.isArray(value.imports)) {
|
|
2711
|
+
for (const dependency of value.imports) visit(dependency, depth + 1);
|
|
2712
|
+
}
|
|
2713
|
+
};
|
|
2714
|
+
for (const input of inputs) visit(input, 0);
|
|
2715
|
+
return [...definitions.values()].sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
2716
|
+
}
|
|
2717
|
+
function composeNamespace(definition) {
|
|
2718
|
+
const all = [localContract(definition), ...definition.imports ?? []];
|
|
2719
|
+
const owners = /* @__PURE__ */ new Map();
|
|
2720
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
2721
|
+
const enums = /* @__PURE__ */ new Map();
|
|
2722
|
+
for (const owner of all) {
|
|
2723
|
+
if (namespaces.has(owner.namespace)) throw new OrmDefinitionError(`duplicate imported namespace '${owner.namespace}'`);
|
|
2724
|
+
namespaces.add(owner.namespace);
|
|
2725
|
+
for (const value of owner.enums) {
|
|
2726
|
+
const previous = enums.get(value.name);
|
|
2727
|
+
if (previous && stableJson(previous) !== stableJson(value)) {
|
|
2728
|
+
throw new OrmDefinitionError(`conflicting imported enum '${value.name}'`);
|
|
2729
|
+
}
|
|
2730
|
+
enums.set(value.name, value);
|
|
2731
|
+
}
|
|
2732
|
+
for (const relation of canonicalRelations(owner)) {
|
|
2733
|
+
if (owners.has(relation.table)) throw new OrmDefinitionError(`ambiguous imported relation '${relation.table}'`);
|
|
2734
|
+
owners.set(relation.table, owner);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
for (const owner of definition.imports?.length ? all : []) {
|
|
2738
|
+
for (const relation of canonicalRelations(owner)) {
|
|
2739
|
+
for (const link of relation.links) {
|
|
2740
|
+
if (link.kind === "toPlatformUser") continue;
|
|
2741
|
+
const target = owners.get(link.target ?? "");
|
|
2742
|
+
if (!target || target.namespace !== (link.namespace ?? owner.namespace)) {
|
|
2743
|
+
throw new OrmDefinitionError(`link '${owner.namespace}/${relation.table}.${link.name}' has no matching imported target`);
|
|
2744
|
+
}
|
|
2745
|
+
if (target.namespace !== owner.namespace && !target.exports?.[link.target ?? ""]) {
|
|
2746
|
+
throw new OrmDefinitionError(`target '${target.namespace}/${link.target}' is not exported`);
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
return {
|
|
2752
|
+
owners,
|
|
2753
|
+
definition: {
|
|
2754
|
+
...localContract(definition),
|
|
2755
|
+
tables: all.flatMap((item) => item.tables),
|
|
2756
|
+
views: all.flatMap((item) => item.views ?? []),
|
|
2757
|
+
enums: [...enums.values()]
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2650
2762
|
// src/authoring/namespace.ts
|
|
2651
2763
|
var NAMESPACE_CONFIG_BRAND = "seq-namespace-config";
|
|
2652
2764
|
function isSeqNamespaceConfig(value) {
|
|
@@ -2662,6 +2774,8 @@ function defineNamespace(input) {
|
|
|
2662
2774
|
namespace: input.namespace,
|
|
2663
2775
|
description: input.description,
|
|
2664
2776
|
capabilities: input.capabilities ?? {},
|
|
2777
|
+
imports: input.imports,
|
|
2778
|
+
exports: input.exports,
|
|
2665
2779
|
file
|
|
2666
2780
|
};
|
|
2667
2781
|
registerNamespaceConfig(config);
|
|
@@ -2706,15 +2820,15 @@ function serializeNamespace(input) {
|
|
|
2706
2820
|
}
|
|
2707
2821
|
const relationsByName = /* @__PURE__ */ new Map();
|
|
2708
2822
|
for (const relation of [...tables, ...views]) {
|
|
2709
|
-
const
|
|
2710
|
-
const existing = relationsByName.get(
|
|
2823
|
+
const name2 = relationName(relation);
|
|
2824
|
+
const existing = relationsByName.get(name2);
|
|
2711
2825
|
if (existing) {
|
|
2712
2826
|
throw new OrmDefinitionError(
|
|
2713
|
-
`duplicate relation "${
|
|
2827
|
+
`duplicate relation "${name2}" (defined as ${relationKind(existing)} in ${describeFile(existing)} and ${relationKind(relation)} in ${describeFile(relation)})`,
|
|
2714
2828
|
relation.file
|
|
2715
2829
|
);
|
|
2716
2830
|
}
|
|
2717
|
-
relationsByName.set(
|
|
2831
|
+
relationsByName.set(name2, relation);
|
|
2718
2832
|
}
|
|
2719
2833
|
const enumsByName = /* @__PURE__ */ new Map();
|
|
2720
2834
|
for (const ref of enums) {
|
|
@@ -2755,6 +2869,8 @@ function serializeNamespace(input) {
|
|
|
2755
2869
|
};
|
|
2756
2870
|
if (config.description !== void 0) candidate.description = config.description;
|
|
2757
2871
|
if (canonicalViews.length > 0) candidate.views = canonicalViews;
|
|
2872
|
+
if (config.imports?.length) candidate.imports = flattenNamespaceImports(config.imports);
|
|
2873
|
+
if (config.exports) candidate.exports = config.exports;
|
|
2758
2874
|
let parsed;
|
|
2759
2875
|
try {
|
|
2760
2876
|
parsed = canonicalDefinitionSchema.parse(candidate);
|
|
@@ -2764,7 +2880,9 @@ function serializeNamespace(input) {
|
|
|
2764
2880
|
}
|
|
2765
2881
|
throw error;
|
|
2766
2882
|
}
|
|
2767
|
-
|
|
2883
|
+
const normalized = normalizeCanonical(parsed);
|
|
2884
|
+
if (normalized.imports?.length) composeNamespace(normalized);
|
|
2885
|
+
return normalized;
|
|
2768
2886
|
}
|
|
2769
2887
|
function computeCanonicalHash(definition) {
|
|
2770
2888
|
return crypto.createHash("sha256").update(HASH_DOMAIN).update(stableJson(definition)).digest("hex");
|
|
@@ -2778,6 +2896,7 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
|
|
|
2778
2896
|
policies: {},
|
|
2779
2897
|
actions: []
|
|
2780
2898
|
};
|
|
2899
|
+
if (table.config.displayName !== void 0) candidate.displayName = table.config.displayName;
|
|
2781
2900
|
if (table.config.description !== void 0) candidate.description = table.config.description;
|
|
2782
2901
|
if (table.config.renamedFrom !== void 0) candidate.renamedFrom = table.config.renamedFrom;
|
|
2783
2902
|
if (table.source !== void 0) candidate.source = table.source;
|
|
@@ -2861,6 +2980,9 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
|
|
|
2861
2980
|
if (table.config.foreignKeys?.length) {
|
|
2862
2981
|
if (table.source) throw new OrmDefinitionError(`synced table "${table.tableName}" cannot enforce foreign keys`, table.file);
|
|
2863
2982
|
candidate.foreignKeys = table.config.foreignKeys.map((fk) => {
|
|
2983
|
+
if (typeof fk.target === "object" && fk.target.kind === "seq-external-object") {
|
|
2984
|
+
throw new OrmDefinitionError("physical foreign keys require a local target; use a cross-namespace ORM link", table.file);
|
|
2985
|
+
}
|
|
2864
2986
|
const target = resolveRelationRef({ ref: fk.target, fail: (message) => {
|
|
2865
2987
|
throw new OrmDefinitionError(message, table.file);
|
|
2866
2988
|
} });
|
|
@@ -3031,6 +3153,19 @@ function buildCanonicalLink({
|
|
|
3031
3153
|
...link.description !== void 0 ? { description: link.description } : {}
|
|
3032
3154
|
};
|
|
3033
3155
|
}
|
|
3156
|
+
if (typeof link.target === "object" && link.target?.kind === "seq-external-object") {
|
|
3157
|
+
if (link.enforce) return fail("cannot enforce a cross-namespace physical foreign key; use an ORM link without enforce");
|
|
3158
|
+
return {
|
|
3159
|
+
name: linkName,
|
|
3160
|
+
kind: link.linkKind,
|
|
3161
|
+
namespace: link.target.namespace,
|
|
3162
|
+
target: link.target.table,
|
|
3163
|
+
on: link.linkKind === "toOne" ? resolveColumnRef({ relation, ref: link.on, role: "link on" }) : link.on,
|
|
3164
|
+
...link.references !== void 0 ? { references: link.linkKind === "toMany" ? resolveColumnRef({ relation, ref: link.references, role: "link references" }) : link.references } : {},
|
|
3165
|
+
enforce: false,
|
|
3166
|
+
...link.description !== void 0 ? { description: link.description } : {}
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3034
3169
|
const target = resolveRelationRef({ ref: link.target, fail });
|
|
3035
3170
|
const targetName = relationName(target);
|
|
3036
3171
|
if (!relationsByName.has(targetName)) {
|
|
@@ -3087,11 +3222,11 @@ function assertAcyclicViewDependencies(views) {
|
|
|
3087
3222
|
views.map((view) => [view.view, new Set(view.dependencies.filter((dependency) => viewNames.has(dependency)))])
|
|
3088
3223
|
);
|
|
3089
3224
|
while (remaining.size > 0) {
|
|
3090
|
-
const ready = [...remaining.entries()].filter(([, dependencies]) => [...dependencies].every((dependency) => !remaining.has(dependency))).map(([
|
|
3225
|
+
const ready = [...remaining.entries()].filter(([, dependencies]) => [...dependencies].every((dependency) => !remaining.has(dependency))).map(([name2]) => name2);
|
|
3091
3226
|
if (ready.length === 0) {
|
|
3092
3227
|
throw new OrmDefinitionError(`view dependencies form a cycle among: ${[...remaining.keys()].sort().join(", ")}`);
|
|
3093
3228
|
}
|
|
3094
|
-
for (const
|
|
3229
|
+
for (const name2 of ready) remaining.delete(name2);
|
|
3095
3230
|
}
|
|
3096
3231
|
}
|
|
3097
3232
|
function toCanonicalPolicy(policy) {
|
|
@@ -3137,8 +3272,8 @@ function syncedContractSurface({
|
|
|
3137
3272
|
namespace,
|
|
3138
3273
|
table,
|
|
3139
3274
|
columns: Object.fromEntries(
|
|
3140
|
-
Object.entries(columns).map(([
|
|
3141
|
-
|
|
3275
|
+
Object.entries(columns).map(([name2, column]) => [
|
|
3276
|
+
name2,
|
|
3142
3277
|
{ type: column.type, nullable: column.nullable, key: column.key }
|
|
3143
3278
|
])
|
|
3144
3279
|
),
|
|
@@ -3159,7 +3294,7 @@ function extractOrmSyncedTableContract({
|
|
|
3159
3294
|
}
|
|
3160
3295
|
if (table.source === void 0) {
|
|
3161
3296
|
throw new OrmDefinitionError(
|
|
3162
|
-
`table "${table.table}" is not a synced table \u2014 extractOrmSyncedTableContract requires source
|
|
3297
|
+
`table "${table.table}" is not a synced table \u2014 extractOrmSyncedTableContract requires a qualified source`
|
|
3163
3298
|
);
|
|
3164
3299
|
}
|
|
3165
3300
|
if (table.columns.length === 0) {
|
|
@@ -3306,6 +3441,53 @@ function purgeModuleCache(root) {
|
|
|
3306
3441
|
if (key.startsWith(prefix)) delete require2.cache[key];
|
|
3307
3442
|
}
|
|
3308
3443
|
}
|
|
3444
|
+
var sqlScalarSchema = zod.z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]);
|
|
3445
|
+
var name = zod.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(63).refine((value) => !["__proto__", "prototype", "constructor"].includes(value), "reserved name");
|
|
3446
|
+
var base = zod.z.object({
|
|
3447
|
+
name,
|
|
3448
|
+
namespace: name,
|
|
3449
|
+
language: zod.z.literal("sql"),
|
|
3450
|
+
kind: zod.z.literal("query"),
|
|
3451
|
+
requires: zod.z.object({ capability: zod.z.string().min(1).max(128) }).strict(),
|
|
3452
|
+
ontology: zod.z.object({ read: zod.z.array(zod.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*\/[A-Za-z_][A-Za-z0-9_]*$/)).min(1).max(256) }).strict(),
|
|
3453
|
+
maxRows: zod.z.number().int().min(1).max(1e4).default(1e3)
|
|
3454
|
+
});
|
|
3455
|
+
var fixed = base.extend({
|
|
3456
|
+
parameters: zod.z.record(name, zod.z.object({ type: sqlScalarSchema, required: zod.z.boolean().default(true) }).strict()).default({}),
|
|
3457
|
+
sql: zod.z.string().min(1).max(65536)
|
|
3458
|
+
}).strict();
|
|
3459
|
+
var runtime = base.extend({ mode: zod.z.literal("runtime") }).strict();
|
|
3460
|
+
var sqlOperationDefinitionSchema = zod.z.union([fixed, runtime]);
|
|
3461
|
+
function defineOperation(input) {
|
|
3462
|
+
return sqlOperationDefinitionSchema.parse(input);
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3465
|
+
// src/authoring/catalog.ts
|
|
3466
|
+
var SEGMENT = /^[^./\\\s][^/\\]*[^./\\\s]$|^[^./\\\s]$/;
|
|
3467
|
+
function validateNamespaceCatalog(catalog, file) {
|
|
3468
|
+
const paths = /* @__PURE__ */ new Set([""]);
|
|
3469
|
+
function visit(folders, parent) {
|
|
3470
|
+
for (const [name2, children] of Object.entries(folders)) {
|
|
3471
|
+
if (!SEGMENT.test(name2) || ["__proto__", "constructor", "prototype"].includes(name2)) {
|
|
3472
|
+
throw new OrmDefinitionError(`invalid catalog folder name "${name2}"`, file);
|
|
3473
|
+
}
|
|
3474
|
+
const path = parent ? `${parent}/${name2}` : name2;
|
|
3475
|
+
paths.add(path);
|
|
3476
|
+
if (children !== null) {
|
|
3477
|
+
if (typeof children !== "object" || Array.isArray(children)) {
|
|
3478
|
+
throw new OrmDefinitionError(`catalog folder "${path}" must contain folders or null`, file);
|
|
3479
|
+
}
|
|
3480
|
+
visit(children, path);
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
visit(catalog.folders, "");
|
|
3485
|
+
for (const [object, path] of Object.entries(catalog.objects)) {
|
|
3486
|
+
if (!object || !paths.has(path)) {
|
|
3487
|
+
throw new OrmDefinitionError(`catalog object "${object}" references missing folder "${path}"`, file);
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3309
3491
|
|
|
3310
3492
|
// src/codegen/types.ts
|
|
3311
3493
|
var WIRE_TYPES = {
|
|
@@ -3337,9 +3519,9 @@ function generateNamespaceTypes(definition) {
|
|
|
3337
3519
|
lines.push(` /** Synced from ${table.source} \u2014 read-only typed contract. */`);
|
|
3338
3520
|
}
|
|
3339
3521
|
lines.push(` ${table.table}: {`);
|
|
3340
|
-
for (const [
|
|
3522
|
+
for (const [name2, column] of columns) {
|
|
3341
3523
|
if (column.deprecated) continue;
|
|
3342
|
-
lines.push(` ${
|
|
3524
|
+
lines.push(` ${name2}: ${columnWireType(column)}`);
|
|
3343
3525
|
}
|
|
3344
3526
|
lines.push(" }");
|
|
3345
3527
|
}
|
|
@@ -3350,8 +3532,8 @@ function generateNamespaceTypes(definition) {
|
|
|
3350
3532
|
function actionTypeLines(definition) {
|
|
3351
3533
|
const actions = definition.tables.flatMap((table) => Object.entries(table.actions ?? {}));
|
|
3352
3534
|
const lines = [`export type ${actionsInterfaceName(definition.namespace)} = {`];
|
|
3353
|
-
for (const [
|
|
3354
|
-
lines.push(` ${
|
|
3535
|
+
for (const [name2, action] of actions.sort(([a], [b]) => a.localeCompare(b))) {
|
|
3536
|
+
lines.push(` ${name2}: { params: ${actionParamsType(action)}; result: ${actionResultType(action)} }`);
|
|
3355
3537
|
}
|
|
3356
3538
|
lines.push("}", "");
|
|
3357
3539
|
return lines;
|
|
@@ -3359,17 +3541,17 @@ function actionTypeLines(definition) {
|
|
|
3359
3541
|
function actionParamsType(action) {
|
|
3360
3542
|
const entries = Object.entries(action.params ?? {});
|
|
3361
3543
|
if (entries.length === 0) return "Record<never, never>";
|
|
3362
|
-
const fields = entries.map(([
|
|
3363
|
-
const
|
|
3364
|
-
return param.optional ? `${
|
|
3544
|
+
const fields = entries.map(([name2, param]) => {
|
|
3545
|
+
const base2 = WIRE_TYPES[param.type];
|
|
3546
|
+
return param.optional ? `${name2}?: ${base2} | null` : `${name2}: ${base2}`;
|
|
3365
3547
|
});
|
|
3366
3548
|
return `{ ${fields.join("; ")} }`;
|
|
3367
3549
|
}
|
|
3368
3550
|
function actionResultType(action) {
|
|
3369
3551
|
if (!action.returns) return "{ rowsAffected: number }";
|
|
3370
|
-
const columns = Object.entries(action.returns).map(([
|
|
3371
|
-
const
|
|
3372
|
-
return
|
|
3552
|
+
const columns = Object.entries(action.returns).map(([name2, type]) => {
|
|
3553
|
+
const base2 = WIRE_TYPES[type];
|
|
3554
|
+
return base2 === "unknown" ? `${name2}: ${base2}` : `${name2}: ${base2} | null`;
|
|
3373
3555
|
});
|
|
3374
3556
|
return `{ rows: Array<{ ${columns.join("; ")} }> }`;
|
|
3375
3557
|
}
|
|
@@ -3383,9 +3565,9 @@ function pascalCase(namespace) {
|
|
|
3383
3565
|
return namespace.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
3384
3566
|
}
|
|
3385
3567
|
function columnWireType(column) {
|
|
3386
|
-
const
|
|
3568
|
+
const base2 = WIRE_TYPES[column.type];
|
|
3387
3569
|
const notNull = column.primary_key === true || column.nullable === false;
|
|
3388
|
-
return notNull ||
|
|
3570
|
+
return notNull || base2 === "unknown" ? base2 : `${base2} | null`;
|
|
3389
3571
|
}
|
|
3390
3572
|
|
|
3391
3573
|
// src/predicate/ast.ts
|
|
@@ -3850,7 +4032,7 @@ function executesIssues(executes, declaredParams, hasReturns) {
|
|
|
3850
4032
|
if (hasReturns && last && !RETURNING_PATTERN.test(last.replace(QUOTED_STRING_PATTERN, "''"))) {
|
|
3851
4033
|
issues.push("the last executes statement must use RETURNING to populate the declared returns columns");
|
|
3852
4034
|
}
|
|
3853
|
-
const undeclared = extractNamedParams(statements.join("\n")).filter((
|
|
4035
|
+
const undeclared = extractNamedParams(statements.join("\n")).filter((name2) => !declaredParams.has(name2));
|
|
3854
4036
|
if (undeclared.length > 0) issues.push(`executes references undeclared params: ${undeclared.join(", ")}`);
|
|
3855
4037
|
return issues;
|
|
3856
4038
|
}
|
|
@@ -3925,19 +4107,19 @@ var PARAM_SCHEMAS = {
|
|
|
3925
4107
|
function buildActionParamsSchema(action) {
|
|
3926
4108
|
const shape = {};
|
|
3927
4109
|
const entries = Object.entries(action.params ?? {});
|
|
3928
|
-
for (const [
|
|
3929
|
-
shape[
|
|
4110
|
+
for (const [name2, param] of entries) {
|
|
4111
|
+
shape[name2] = param.optional ? PARAM_SCHEMAS[param.type].nullish() : PARAM_SCHEMAS[param.type];
|
|
3930
4112
|
}
|
|
3931
4113
|
return zod.z.strictObject(shape).superRefine((obj, ctx) => {
|
|
3932
|
-
for (const [
|
|
3933
|
-
if (!param.optional && param.type === "jsonb" && obj[
|
|
3934
|
-
ctx.addIssue({ code: "custom", message: "Required", path: [
|
|
4114
|
+
for (const [name2, param] of entries) {
|
|
4115
|
+
if (!param.optional && param.type === "jsonb" && obj[name2] === void 0) {
|
|
4116
|
+
ctx.addIssue({ code: "custom", message: "Required", path: [name2] });
|
|
3935
4117
|
}
|
|
3936
4118
|
}
|
|
3937
4119
|
});
|
|
3938
4120
|
}
|
|
3939
4121
|
var actionParamOrder = (action) => Object.keys(action.params ?? {});
|
|
3940
|
-
var actionParamArg = (
|
|
4122
|
+
var actionParamArg = (name2) => `p_${name2}`;
|
|
3941
4123
|
function compileActionStatements(action, renderParam = actionParamArg) {
|
|
3942
4124
|
return actionStatementSources(action.executes).map((statement) => compileStatement(statement, renderParam));
|
|
3943
4125
|
}
|
|
@@ -3988,10 +4170,12 @@ function compilePredIrToSql(pred, options = {}) {
|
|
|
3988
4170
|
case "cmp":
|
|
3989
4171
|
return compileCmp(pred.op, pred.column, pred.value, options);
|
|
3990
4172
|
case "capability":
|
|
3991
|
-
return `(${capabilityExpression(pred.key)})`;
|
|
4173
|
+
return `(${capabilityExpression(pred.key, pred.namespace ?? options.namespace)})`;
|
|
3992
4174
|
case "role":
|
|
3993
|
-
return `(${roleExpression(pred.key)})`;
|
|
4175
|
+
return `(${roleExpression(pred.key, pred.namespace ?? options.namespace)})`;
|
|
3994
4176
|
case "exists":
|
|
4177
|
+
if (options.exists) return options.exists(pred);
|
|
4178
|
+
throw new OrmDefinitionError("synced view read policy cannot use 'exists' without a relationship resolver");
|
|
3995
4179
|
case "aggPredicate":
|
|
3996
4180
|
case "dynamicFilter":
|
|
3997
4181
|
throw new OrmDefinitionError(
|
|
@@ -4010,7 +4194,8 @@ function compileCmp(op, column, value, options) {
|
|
|
4010
4194
|
const physical2 = physicalColumn(column, options);
|
|
4011
4195
|
const declared = options.columnTypes?.[physical2] ?? options.columnTypes?.[column];
|
|
4012
4196
|
const isEnum = typeof declared === "object" && declared !== null;
|
|
4013
|
-
const
|
|
4197
|
+
const qualified = options.alias ? `${quoteIdentifier(options.alias)}.${quoteIdentifier(physical2)}` : quoteIdentifier(physical2);
|
|
4198
|
+
const col = isEnum ? `${qualified}::text` : qualified;
|
|
4014
4199
|
const columnType = isEnum ? "text" : declared;
|
|
4015
4200
|
switch (op) {
|
|
4016
4201
|
case "eq":
|
|
@@ -4094,22 +4279,22 @@ function returnProjection(returns, lastStatement) {
|
|
|
4094
4279
|
const projection = Object.keys(returns).map(quoteIdentifier).join(", ");
|
|
4095
4280
|
return `WITH orm_returning AS (${lastStatement}) SELECT ${projection} FROM orm_returning`;
|
|
4096
4281
|
}
|
|
4097
|
-
var allActions = (definition) => definition.tables.flatMap((table) => Object.entries(table.actions ?? {}).map(([
|
|
4282
|
+
var allActions = (definition) => definition.tables.flatMap((table) => Object.entries(table.actions ?? {}).map(([name2, action]) => ({ name: name2, action }))).sort((a, b) => a.name.localeCompare(b.name));
|
|
4098
4283
|
function actionFunctionStatements(definition) {
|
|
4099
4284
|
const schema = ormSchemaName(definition.namespace);
|
|
4100
|
-
return [dropAllActionFunctions(schema), ...allActions(definition).flatMap(({ name, action }) => createActionFunction(definition,
|
|
4285
|
+
return [dropAllActionFunctions(schema), ...allActions(definition).flatMap(({ name: name2, action }) => createActionFunction(definition, name2, action))];
|
|
4101
4286
|
}
|
|
4102
4287
|
function actionPrepareChecks(definition) {
|
|
4103
4288
|
const actions = allActions(definition);
|
|
4104
4289
|
if (actions.length === 0) return [];
|
|
4105
4290
|
const statements = [`SET LOCAL search_path TO ${quoteIdentifier(ormSchemaName(definition.namespace))}`];
|
|
4106
|
-
for (const { name, action } of actions) {
|
|
4291
|
+
for (const { name: name2, action } of actions) {
|
|
4107
4292
|
const order = actionParamOrder(action);
|
|
4108
4293
|
const signature = order.length > 0 ? ` (${order.map((param) => sqlTypeName(action.params[param].type)).join(", ")})` : "";
|
|
4109
4294
|
const compiled = compileActionStatements(action, (param) => `$${order.indexOf(param) + 1}`);
|
|
4110
4295
|
compiled.forEach((sql7, index3) => {
|
|
4111
4296
|
const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns, sql7) : sql7;
|
|
4112
|
-
statements.push(`PREPARE ${quoteIdentifier(`orm_check_${
|
|
4297
|
+
statements.push(`PREPARE ${quoteIdentifier(`orm_check_${name2}_${index3}`)}${signature} AS ${checked}`);
|
|
4113
4298
|
});
|
|
4114
4299
|
}
|
|
4115
4300
|
return statements;
|
|
@@ -4130,9 +4315,9 @@ BEGIN
|
|
|
4130
4315
|
END
|
|
4131
4316
|
$orm_reconcile_actions$`;
|
|
4132
4317
|
}
|
|
4133
|
-
function createActionFunction(definition,
|
|
4318
|
+
function createActionFunction(definition, name2, action) {
|
|
4134
4319
|
const schema = quoteIdentifier(ormSchemaName(definition.namespace));
|
|
4135
|
-
const fn = `${schema}.${quoteIdentifier(actionFunctionName(
|
|
4320
|
+
const fn = `${schema}.${quoteIdentifier(actionFunctionName(name2))}`;
|
|
4136
4321
|
const order = actionParamOrder(action);
|
|
4137
4322
|
const signature = order.map((paramName) => `${quoteIdentifier(actionParamArg(paramName))} ${sqlTypeName(action.params[paramName].type)}`).join(", ");
|
|
4138
4323
|
const argTypes = order.map((paramName) => sqlTypeName(action.params[paramName].type)).join(", ");
|
|
@@ -4152,7 +4337,7 @@ AS $orm_action$
|
|
|
4152
4337
|
-- columns; resolve unqualified names to the column.
|
|
4153
4338
|
#variable_conflict use_column
|
|
4154
4339
|
BEGIN
|
|
4155
|
-
${[functionGuards(definition,
|
|
4340
|
+
${[functionGuards(definition, name2, action), ...leading, ` RETURN QUERY ${returnProjection(action.returns, last)};`].filter(Boolean).join("\n")}
|
|
4156
4341
|
END
|
|
4157
4342
|
$orm_action$`
|
|
4158
4343
|
) : (
|
|
@@ -4166,7 +4351,7 @@ AS $orm_action$
|
|
|
4166
4351
|
DECLARE
|
|
4167
4352
|
v_count bigint;
|
|
4168
4353
|
BEGIN
|
|
4169
|
-
${[functionGuards(definition,
|
|
4354
|
+
${[functionGuards(definition, name2, action), ...leading, ` ${last};`].filter(Boolean).join("\n")}
|
|
4170
4355
|
GET DIAGNOSTICS v_count = ROW_COUNT;
|
|
4171
4356
|
RETURN v_count;
|
|
4172
4357
|
END
|
|
@@ -4178,13 +4363,13 @@ $orm_action$`
|
|
|
4178
4363
|
`GRANT EXECUTE ON FUNCTION ${fn}(${argTypes}) TO ${role}`
|
|
4179
4364
|
];
|
|
4180
4365
|
}
|
|
4181
|
-
function functionGuards(definition,
|
|
4366
|
+
function functionGuards(definition, name2, action) {
|
|
4182
4367
|
const lines = [];
|
|
4183
4368
|
const requires = compileActionRequires(action);
|
|
4184
4369
|
if (requires) {
|
|
4185
4370
|
lines.push(
|
|
4186
4371
|
` IF NOT (${requires}) THEN`,
|
|
4187
|
-
` RAISE EXCEPTION 'action ${
|
|
4372
|
+
` RAISE EXCEPTION 'action ${name2} requires a grant the caller does not hold' USING ERRCODE = ${quoteLiteral(ACTION_REQUIRES_DENIED_SQLSTATE)};`,
|
|
4188
4373
|
` END IF;`
|
|
4189
4374
|
);
|
|
4190
4375
|
}
|
|
@@ -4236,7 +4421,7 @@ var tableFileSchema = zod.z.strictObject({
|
|
|
4236
4421
|
table: identifierSchema,
|
|
4237
4422
|
description: zod.z.string().optional(),
|
|
4238
4423
|
columns: zod.z.record(identifierSchema, columnSchema).optional(),
|
|
4239
|
-
source: zod.z.string().regex(SYNCED_SOURCE_PATTERN, "source must
|
|
4424
|
+
source: zod.z.string().regex(SYNCED_SOURCE_PATTERN, "source must be a qualified lowercase schema.table identifier").optional(),
|
|
4240
4425
|
links: zod.z.record(identifierSchema, linkSchema).optional(),
|
|
4241
4426
|
indexes: zod.z.array(indexSchema).optional(),
|
|
4242
4427
|
policies: policiesSchema.optional(),
|
|
@@ -4245,12 +4430,12 @@ var tableFileSchema = zod.z.strictObject({
|
|
|
4245
4430
|
if (RESERVED_TABLE_NAMES.has(table.table)) {
|
|
4246
4431
|
ctx.addIssue({ code: "custom", message: `'${table.table}' is a reserved table name`, path: ["table"] });
|
|
4247
4432
|
}
|
|
4248
|
-
for (const
|
|
4249
|
-
if (RESERVED_COLUMN_NAMES.has(
|
|
4433
|
+
for (const name2 of Object.keys(table.columns ?? {})) {
|
|
4434
|
+
if (RESERVED_COLUMN_NAMES.has(name2)) {
|
|
4250
4435
|
ctx.addIssue({
|
|
4251
4436
|
code: "custom",
|
|
4252
|
-
message: `column '${
|
|
4253
|
-
path: ["columns",
|
|
4437
|
+
message: `column '${name2}' collides with the Data API's reserved query parameters (select, order, limit, offset) \u2014 it could never be filtered`,
|
|
4438
|
+
path: ["columns", name2]
|
|
4254
4439
|
});
|
|
4255
4440
|
}
|
|
4256
4441
|
}
|
|
@@ -4283,41 +4468,41 @@ function validateSyncedTable(table, ctx) {
|
|
|
4283
4468
|
const columns = table.columns ?? {};
|
|
4284
4469
|
const names = Object.keys(columns);
|
|
4285
4470
|
if (names.length === 0) return;
|
|
4286
|
-
const primaryKeys = names.filter((
|
|
4471
|
+
const primaryKeys = names.filter((name2) => columns[name2]?.primary_key);
|
|
4287
4472
|
if (primaryKeys.length !== 1) {
|
|
4288
4473
|
ctx.addIssue({ code: "custom", message: "exactly one column must be primary_key", path: ["columns"] });
|
|
4289
4474
|
}
|
|
4290
4475
|
validateRenames(columns, names, ctx);
|
|
4291
|
-
for (const
|
|
4292
|
-
const column = columns[
|
|
4476
|
+
for (const name2 of names) {
|
|
4477
|
+
const column = columns[name2];
|
|
4293
4478
|
if (!column) continue;
|
|
4294
4479
|
const cast = column.cast ? castParts(column.cast) : null;
|
|
4295
|
-
if (column.cast && cast?.column !==
|
|
4480
|
+
if (column.cast && cast?.column !== name2) {
|
|
4296
4481
|
ctx.addIssue({
|
|
4297
4482
|
code: "custom",
|
|
4298
4483
|
message: "cast must reference the column it is declared on",
|
|
4299
|
-
path: ["columns",
|
|
4484
|
+
path: ["columns", name2, "cast"]
|
|
4300
4485
|
});
|
|
4301
4486
|
}
|
|
4302
4487
|
if (cast && cast.type !== column.type) {
|
|
4303
4488
|
ctx.addIssue({
|
|
4304
4489
|
code: "custom",
|
|
4305
4490
|
message: `cast target '${cast.type}' must match the declared type '${column.type}'`,
|
|
4306
|
-
path: ["columns",
|
|
4491
|
+
path: ["columns", name2, "cast"]
|
|
4307
4492
|
});
|
|
4308
4493
|
}
|
|
4309
4494
|
}
|
|
4310
4495
|
}
|
|
4311
4496
|
function validateRenames(columns, names, ctx) {
|
|
4312
4497
|
const renameSources = /* @__PURE__ */ new Map();
|
|
4313
|
-
for (const
|
|
4314
|
-
const renamedFrom = columns[
|
|
4498
|
+
for (const name2 of names) {
|
|
4499
|
+
const renamedFrom = columns[name2]?.renamed_from;
|
|
4315
4500
|
if (!renamedFrom) continue;
|
|
4316
|
-
if (renamedFrom ===
|
|
4501
|
+
if (renamedFrom === name2 || names.includes(renamedFrom)) {
|
|
4317
4502
|
ctx.addIssue({
|
|
4318
4503
|
code: "custom",
|
|
4319
4504
|
message: "renamed_from must name a column that no longer exists",
|
|
4320
|
-
path: ["columns",
|
|
4505
|
+
path: ["columns", name2, "renamed_from"]
|
|
4321
4506
|
});
|
|
4322
4507
|
}
|
|
4323
4508
|
const claimed = renameSources.get(renamedFrom);
|
|
@@ -4325,10 +4510,10 @@ function validateRenames(columns, names, ctx) {
|
|
|
4325
4510
|
ctx.addIssue({
|
|
4326
4511
|
code: "custom",
|
|
4327
4512
|
message: `renamed_from '${renamedFrom}' is already claimed by '${claimed}'`,
|
|
4328
|
-
path: ["columns",
|
|
4513
|
+
path: ["columns", name2, "renamed_from"]
|
|
4329
4514
|
});
|
|
4330
4515
|
}
|
|
4331
|
-
renameSources.set(renamedFrom,
|
|
4516
|
+
renameSources.set(renamedFrom, name2);
|
|
4332
4517
|
}
|
|
4333
4518
|
}
|
|
4334
4519
|
function validateColumnTable(table, ctx) {
|
|
@@ -4338,27 +4523,27 @@ function validateColumnTable(table, ctx) {
|
|
|
4338
4523
|
ctx.addIssue({ code: "custom", message: "a table must declare columns or a source", path: ["columns"] });
|
|
4339
4524
|
return;
|
|
4340
4525
|
}
|
|
4341
|
-
const primaryKeys = names.filter((
|
|
4526
|
+
const primaryKeys = names.filter((name2) => columns[name2]?.primary_key);
|
|
4342
4527
|
if (primaryKeys.length !== 1) {
|
|
4343
4528
|
ctx.addIssue({ code: "custom", message: "exactly one column must be primary_key", path: ["columns"] });
|
|
4344
4529
|
}
|
|
4345
4530
|
validateRenames(columns, names, ctx);
|
|
4346
|
-
for (const
|
|
4347
|
-
const column = columns[
|
|
4531
|
+
for (const name2 of names) {
|
|
4532
|
+
const column = columns[name2];
|
|
4348
4533
|
if (!column) continue;
|
|
4349
4534
|
const cast = column.cast ? castParts(column.cast) : null;
|
|
4350
|
-
if (column.cast && cast?.column !==
|
|
4535
|
+
if (column.cast && cast?.column !== name2) {
|
|
4351
4536
|
ctx.addIssue({
|
|
4352
4537
|
code: "custom",
|
|
4353
4538
|
message: "cast must reference the column it is declared on",
|
|
4354
|
-
path: ["columns",
|
|
4539
|
+
path: ["columns", name2, "cast"]
|
|
4355
4540
|
});
|
|
4356
4541
|
}
|
|
4357
4542
|
if (cast && cast.type !== column.type) {
|
|
4358
4543
|
ctx.addIssue({
|
|
4359
4544
|
code: "custom",
|
|
4360
4545
|
message: `cast target '${cast.type}' must match the declared type '${column.type}'`,
|
|
4361
|
-
path: ["columns",
|
|
4546
|
+
path: ["columns", name2, "cast"]
|
|
4362
4547
|
});
|
|
4363
4548
|
}
|
|
4364
4549
|
}
|
|
@@ -4383,22 +4568,22 @@ function validateColumnTable(table, ctx) {
|
|
|
4383
4568
|
}
|
|
4384
4569
|
function validateLinks(table, ctx) {
|
|
4385
4570
|
const columnNames = Object.keys(table.columns ?? {});
|
|
4386
|
-
for (const [
|
|
4571
|
+
for (const [name2, link] of Object.entries(table.links ?? {})) {
|
|
4387
4572
|
if (columnNames.length > 0 && !columnNames.includes(link.on)) {
|
|
4388
|
-
ctx.addIssue({ code: "custom", message: `link references unknown column '${link.on}'`, path: ["links",
|
|
4573
|
+
ctx.addIssue({ code: "custom", message: `link references unknown column '${link.on}'`, path: ["links", name2, "on"] });
|
|
4389
4574
|
}
|
|
4390
4575
|
if (link.enforce && link.to.includes(".")) {
|
|
4391
4576
|
ctx.addIssue({
|
|
4392
4577
|
code: "custom",
|
|
4393
4578
|
message: "enforce is only valid for links inside the namespace; cross-boundary links stay soft",
|
|
4394
|
-
path: ["links",
|
|
4579
|
+
path: ["links", name2, "enforce"]
|
|
4395
4580
|
});
|
|
4396
4581
|
}
|
|
4397
4582
|
if (link.enforce && table.source) {
|
|
4398
4583
|
ctx.addIssue({
|
|
4399
4584
|
code: "custom",
|
|
4400
4585
|
message: "a synced table cannot enforce foreign keys \u2014 its DDL is owned by the sync",
|
|
4401
|
-
path: ["links",
|
|
4586
|
+
path: ["links", name2, "enforce"]
|
|
4402
4587
|
});
|
|
4403
4588
|
}
|
|
4404
4589
|
}
|
|
@@ -4413,32 +4598,32 @@ function validateDerivedNameLengths(table, ctx) {
|
|
|
4413
4598
|
});
|
|
4414
4599
|
}
|
|
4415
4600
|
for (const [position, index3] of (table.indexes ?? []).entries()) {
|
|
4416
|
-
const
|
|
4417
|
-
if (
|
|
4601
|
+
const name2 = indexName(table.table, index3);
|
|
4602
|
+
if (name2.length > MAX_IDENTIFIER_LENGTH) {
|
|
4418
4603
|
ctx.addIssue({
|
|
4419
4604
|
code: "custom",
|
|
4420
|
-
message: `derived index name '${
|
|
4605
|
+
message: `derived index name '${name2}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 set a shorter explicit name`,
|
|
4421
4606
|
path: ["indexes", position]
|
|
4422
4607
|
});
|
|
4423
4608
|
}
|
|
4424
4609
|
}
|
|
4425
|
-
for (const [
|
|
4426
|
-
const constraint = foreignKeyName(table.table,
|
|
4610
|
+
for (const [name2, link] of Object.entries(table.links ?? {})) {
|
|
4611
|
+
const constraint = foreignKeyName(table.table, name2);
|
|
4427
4612
|
if (link.enforce && constraint.length > MAX_IDENTIFIER_LENGTH) {
|
|
4428
4613
|
ctx.addIssue({
|
|
4429
4614
|
code: "custom",
|
|
4430
4615
|
message: `derived foreign-key name '${constraint}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 shorten the link or table name`,
|
|
4431
|
-
path: ["links",
|
|
4616
|
+
path: ["links", name2]
|
|
4432
4617
|
});
|
|
4433
4618
|
}
|
|
4434
4619
|
}
|
|
4435
|
-
for (const
|
|
4436
|
-
const fnName = `action_${
|
|
4620
|
+
for (const name2 of Object.keys(table.actions ?? {})) {
|
|
4621
|
+
const fnName = `action_${name2}`;
|
|
4437
4622
|
if (fnName.length > MAX_IDENTIFIER_LENGTH) {
|
|
4438
4623
|
ctx.addIssue({
|
|
4439
4624
|
code: "custom",
|
|
4440
4625
|
message: `derived action function name '${fnName}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 shorten the action name`,
|
|
4441
|
-
path: ["actions",
|
|
4626
|
+
path: ["actions", name2]
|
|
4442
4627
|
});
|
|
4443
4628
|
}
|
|
4444
4629
|
}
|
|
@@ -4531,7 +4716,7 @@ function policyStatements(schema, table) {
|
|
|
4531
4716
|
const policyName2 = (suffix) => quoteIdentifier(policyName(table.table, suffix));
|
|
4532
4717
|
const statements = POLICY_SUFFIXES.map((suffix) => `DROP POLICY IF EXISTS ${policyName2(suffix)} ON ${qualified}`);
|
|
4533
4718
|
const columnTypes = Object.fromEntries(
|
|
4534
|
-
Object.entries(table.columns ?? {}).map(([
|
|
4719
|
+
Object.entries(table.columns ?? {}).map(([name2, column]) => [name2, column.type])
|
|
4535
4720
|
);
|
|
4536
4721
|
const read = table.policies?.read;
|
|
4537
4722
|
if (read) {
|
|
@@ -4594,12 +4779,19 @@ function syncedViewStatements({
|
|
|
4594
4779
|
const readPolicyIr = readPolicyIrByTable?.get(table.table);
|
|
4595
4780
|
const readPolicy = table.policies?.read;
|
|
4596
4781
|
const whereClause = readPolicyIr ? ` WHERE ${compilePredIrToSql(readPolicyIr, {
|
|
4782
|
+
namespace: definition.namespace,
|
|
4597
4783
|
columnTypes,
|
|
4598
4784
|
columnNames: columnNamesByTable?.get(table.table)
|
|
4599
4785
|
})}` : readPolicy ? ` WHERE ${compilePredicate(readPolicy.ast, narrowToV1ColumnTypes(columnTypes))}` : " WHERE false";
|
|
4600
4786
|
if (backingPrivileges === "grant") {
|
|
4601
4787
|
statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical2.schema)} TO ${owner}`);
|
|
4602
4788
|
statements.push(`GRANT SELECT ON ${physicalTarget} TO ${owner}`);
|
|
4789
|
+
statements.push(`DO ${quoteLiteral(`BEGIN
|
|
4790
|
+
IF NOT (pg_catalog.has_schema_privilege(${quoteLiteral(executionOwnerRole)}, ${quoteLiteral(physical2.schema)}, 'USAGE')
|
|
4791
|
+
AND pg_catalog.has_table_privilege(${quoteLiteral(executionOwnerRole)}, ${quoteLiteral(physicalTarget)}, 'SELECT')) THEN
|
|
4792
|
+
RAISE EXCEPTION 'Synced backing owner lacks effective USAGE/SELECT; repair managed serving reader access before ORM apply' USING ERRCODE = '42501';
|
|
4793
|
+
END IF;
|
|
4794
|
+
END`)}`);
|
|
4603
4795
|
}
|
|
4604
4796
|
statements.push(
|
|
4605
4797
|
`CREATE OR REPLACE VIEW ${viewName} AS SELECT * FROM ${physicalTarget}${whereClause}`
|
|
@@ -4615,8 +4807,8 @@ function mergeSyncedViewColumnTypes({
|
|
|
4615
4807
|
columnNames
|
|
4616
4808
|
}) {
|
|
4617
4809
|
const fromTable = {};
|
|
4618
|
-
for (const [
|
|
4619
|
-
if (def?.type) fromTable[
|
|
4810
|
+
for (const [name2, def] of Object.entries(table.columns ?? {})) {
|
|
4811
|
+
if (def?.type) fromTable[name2] = def.type;
|
|
4620
4812
|
}
|
|
4621
4813
|
if (columnNames) {
|
|
4622
4814
|
for (const [logical, physical2] of Object.entries(columnNames)) {
|
|
@@ -4670,21 +4862,21 @@ function syncedBackingRevokeStatements({
|
|
|
4670
4862
|
return statements;
|
|
4671
4863
|
}
|
|
4672
4864
|
var FACTORIES = {
|
|
4673
|
-
uuid: (
|
|
4674
|
-
text: (
|
|
4675
|
-
integer: (
|
|
4865
|
+
uuid: (name2) => pgCore.uuid(name2),
|
|
4866
|
+
text: (name2) => pgCore.text(name2),
|
|
4867
|
+
integer: (name2) => pgCore.integer(name2),
|
|
4676
4868
|
// Mode 'bigint' keeps int8 lossless — mode 'number' silently rounds past
|
|
4677
4869
|
// 2^53. The read executor serializes BigInt to string for JSON.
|
|
4678
|
-
bigint: (
|
|
4679
|
-
numeric: (
|
|
4680
|
-
double: (
|
|
4681
|
-
boolean: (
|
|
4682
|
-
timestamptz: (
|
|
4683
|
-
date: (
|
|
4684
|
-
jsonb: (
|
|
4870
|
+
bigint: (name2) => pgCore.bigint(name2, { mode: "bigint" }),
|
|
4871
|
+
numeric: (name2) => pgCore.numeric(name2),
|
|
4872
|
+
double: (name2) => pgCore.doublePrecision(name2),
|
|
4873
|
+
boolean: (name2) => pgCore.boolean(name2),
|
|
4874
|
+
timestamptz: (name2) => pgCore.timestamp(name2, { withTimezone: true }),
|
|
4875
|
+
date: (name2) => pgCore.date(name2),
|
|
4876
|
+
jsonb: (name2) => pgCore.jsonb(name2)
|
|
4685
4877
|
};
|
|
4686
|
-
function buildColumn(
|
|
4687
|
-
let column = FACTORIES[definition.type](
|
|
4878
|
+
function buildColumn(name2, definition) {
|
|
4879
|
+
let column = FACTORIES[definition.type](name2);
|
|
4688
4880
|
if (definition.primary_key) column = column.primaryKey();
|
|
4689
4881
|
if (definition.nullable === false && !definition.primary_key) column = column.notNull();
|
|
4690
4882
|
if (definition.unique && !definition.primary_key) column = column.unique();
|
|
@@ -4715,7 +4907,7 @@ function buildDrizzleEntities(definition) {
|
|
|
4715
4907
|
}
|
|
4716
4908
|
function buildTable(schema, table, built) {
|
|
4717
4909
|
const columns = Object.fromEntries(
|
|
4718
|
-
Object.entries(table.columns ?? {}).map(([
|
|
4910
|
+
Object.entries(table.columns ?? {}).map(([name2, column]) => [name2, buildColumn(name2, column)])
|
|
4719
4911
|
);
|
|
4720
4912
|
const drizzleTable = schema.table(table.table, columns, (self) => [
|
|
4721
4913
|
...buildIndexes(table, self),
|
|
@@ -4725,21 +4917,21 @@ function buildTable(schema, table, built) {
|
|
|
4725
4917
|
}
|
|
4726
4918
|
function buildIndexes(table, self) {
|
|
4727
4919
|
return (table.indexes ?? []).map((definition) => {
|
|
4728
|
-
const
|
|
4729
|
-
const factory = definition.unique ? pgCore.uniqueIndex(
|
|
4920
|
+
const name2 = indexName(table.table, definition);
|
|
4921
|
+
const factory = definition.unique ? pgCore.uniqueIndex(name2) : pgCore.index(name2);
|
|
4730
4922
|
const columns = definition.columns.map((column) => self[column]);
|
|
4731
4923
|
return factory.on(...columns);
|
|
4732
4924
|
});
|
|
4733
4925
|
}
|
|
4734
4926
|
function buildForeignKeys(table, self, built) {
|
|
4735
|
-
return Object.entries(table.links ?? {}).filter(([, link]) => link.enforce).map(([
|
|
4927
|
+
return Object.entries(table.links ?? {}).filter(([, link]) => link.enforce).map(([name2, link]) => {
|
|
4736
4928
|
const target = link.to === table.table ? self : built.get(link.to);
|
|
4737
4929
|
const targetColumn = target && findPrimaryKeyColumn(target);
|
|
4738
4930
|
if (!target || !targetColumn) {
|
|
4739
|
-
throw new OrmDefinitionError(`enforced link '${
|
|
4931
|
+
throw new OrmDefinitionError(`enforced link '${name2}' on '${table.table}' has no built target '${link.to}'`);
|
|
4740
4932
|
}
|
|
4741
4933
|
return pgCore.foreignKey({
|
|
4742
|
-
name: foreignKeyName(table.table,
|
|
4934
|
+
name: foreignKeyName(table.table, name2),
|
|
4743
4935
|
columns: [self[link.on]],
|
|
4744
4936
|
foreignColumns: [targetColumn]
|
|
4745
4937
|
});
|
|
@@ -4796,21 +4988,21 @@ function diffStatements(previous, current) {
|
|
|
4796
4988
|
function assertUnambiguousDiff(previous, current) {
|
|
4797
4989
|
const previousTables = tablesOf(previous);
|
|
4798
4990
|
const currentTables = tablesOf(current);
|
|
4799
|
-
const removedTables = [...previousTables.keys()].filter((
|
|
4800
|
-
const addedTables = [...currentTables.keys()].filter((
|
|
4991
|
+
const removedTables = [...previousTables.keys()].filter((name2) => !currentTables.has(name2));
|
|
4992
|
+
const addedTables = [...currentTables.keys()].filter((name2) => !previousTables.has(name2));
|
|
4801
4993
|
if (removedTables.length > 0 && addedTables.length > 0) {
|
|
4802
4994
|
throw new OrmDefinitionError(
|
|
4803
4995
|
`ambiguous diff: table(s) added (${addedTables.join(", ")}) and removed (${removedTables.join(", ")}) in the same change \u2014 ship the add and the drop as separate changes`
|
|
4804
4996
|
);
|
|
4805
4997
|
}
|
|
4806
|
-
for (const [
|
|
4807
|
-
const currentColumns = currentTables.get(
|
|
4998
|
+
for (const [name2, previousColumns] of previousTables) {
|
|
4999
|
+
const currentColumns = currentTables.get(name2);
|
|
4808
5000
|
if (!currentColumns) continue;
|
|
4809
5001
|
const removed = [...previousColumns].filter((column) => !currentColumns.has(column));
|
|
4810
5002
|
const added = [...currentColumns].filter((column) => !previousColumns.has(column));
|
|
4811
5003
|
if (removed.length > 0 && added.length > 0) {
|
|
4812
5004
|
throw new OrmDefinitionError(
|
|
4813
|
-
`ambiguous diff on '${
|
|
5005
|
+
`ambiguous diff on '${name2}': column(s) added (${added.join(", ")}) and removed (${removed.join(", ")}) in the same change \u2014 declare \`renamed_from\` for a rename, or ship the add and the drop as separate changes`
|
|
4814
5006
|
);
|
|
4815
5007
|
}
|
|
4816
5008
|
}
|
|
@@ -4829,33 +5021,33 @@ function applyMigrationHints(previous, definition) {
|
|
|
4829
5021
|
for (const table of columnTables(definition)) {
|
|
4830
5022
|
const snapshotTable = findTable(adjusted, schemaName, table.table);
|
|
4831
5023
|
if (!snapshotTable) continue;
|
|
4832
|
-
for (const [
|
|
5024
|
+
for (const [name2, column] of Object.entries(table.columns ?? {})) {
|
|
4833
5025
|
if (column.renamed_from) {
|
|
4834
5026
|
const previousColumn = snapshotTable.columns[column.renamed_from];
|
|
4835
|
-
if (previousColumn && snapshotTable.columns[
|
|
5027
|
+
if (previousColumn && snapshotTable.columns[name2]) {
|
|
4836
5028
|
throw new OrmDefinitionError(
|
|
4837
|
-
`'${table.table}.${
|
|
5029
|
+
`'${table.table}.${name2}' declares renamed_from '${column.renamed_from}', but both columns exist in the previous schema \u2014 a rename cannot overwrite a live column`
|
|
4838
5030
|
);
|
|
4839
5031
|
}
|
|
4840
5032
|
if (previousColumn) {
|
|
4841
5033
|
statements.push(
|
|
4842
|
-
`ALTER TABLE ${qualified(table.table)} RENAME COLUMN ${quoteIdentifier(column.renamed_from)} TO ${quoteIdentifier(
|
|
5034
|
+
`ALTER TABLE ${qualified(table.table)} RENAME COLUMN ${quoteIdentifier(column.renamed_from)} TO ${quoteIdentifier(name2)}`
|
|
4843
5035
|
);
|
|
4844
5036
|
delete snapshotTable.columns[column.renamed_from];
|
|
4845
|
-
snapshotTable.columns[
|
|
4846
|
-
} else if (!snapshotTable.columns[
|
|
5037
|
+
snapshotTable.columns[name2] = { ...previousColumn, name: name2 };
|
|
5038
|
+
} else if (!snapshotTable.columns[name2]) {
|
|
4847
5039
|
throw new OrmDefinitionError(
|
|
4848
|
-
`'${table.table}.${
|
|
5040
|
+
`'${table.table}.${name2}' declares renamed_from '${column.renamed_from}', but the previous schema has neither column \u2014 check the declared source name`
|
|
4849
5041
|
);
|
|
4850
5042
|
}
|
|
4851
5043
|
}
|
|
4852
5044
|
const cast = column.cast ? castParts(column.cast) : null;
|
|
4853
5045
|
if (cast) {
|
|
4854
|
-
const current = snapshotTable.columns[
|
|
5046
|
+
const current = snapshotTable.columns[name2];
|
|
4855
5047
|
const targetType = sqlTypeName(column.type);
|
|
4856
5048
|
if (current && current.type !== targetType) {
|
|
4857
5049
|
statements.push(
|
|
4858
|
-
`ALTER TABLE ${qualified(table.table)} ALTER COLUMN ${quoteIdentifier(
|
|
5050
|
+
`ALTER TABLE ${qualified(table.table)} ALTER COLUMN ${quoteIdentifier(name2)} TYPE ${targetType} USING ${quoteIdentifier(name2)}::${targetType}`
|
|
4859
5051
|
);
|
|
4860
5052
|
current.type = targetType;
|
|
4861
5053
|
}
|
|
@@ -4896,8 +5088,8 @@ function materializedRefreshFunctions(canonical) {
|
|
|
4896
5088
|
}
|
|
4897
5089
|
return functions;
|
|
4898
5090
|
}
|
|
4899
|
-
function refreshFunctionSql({ schema, name, table, compute, columns, pk }) {
|
|
4900
|
-
const fn = `${quoteIdentifier(schema)}.${quoteIdentifier(materializedRefreshFunctionName(
|
|
5091
|
+
function refreshFunctionSql({ schema, name: name2, table, compute, columns, pk }) {
|
|
5092
|
+
const fn = `${quoteIdentifier(schema)}.${quoteIdentifier(materializedRefreshFunctionName(name2))}`;
|
|
4901
5093
|
const target = `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
|
|
4902
5094
|
const source = `${quoteIdentifier(schema)}.${quoteIdentifier(compute)}`;
|
|
4903
5095
|
const list = columns.map(quoteIdentifier).join(", ");
|
|
@@ -4906,7 +5098,7 @@ function refreshFunctionSql({ schema, name, table, compute, columns, pk }) {
|
|
|
4906
5098
|
const matches = pk.map((column) => `c.${quoteIdentifier(column)} = m.${quoteIdentifier(column)}`).join(" AND ");
|
|
4907
5099
|
const onConflict = others.length === 0 ? "DO NOTHING" : `DO UPDATE SET ${others.map((column) => `${quoteIdentifier(column)} = EXCLUDED.${quoteIdentifier(column)}`).join(", ")}
|
|
4908
5100
|
WHERE (${others.map((column) => `m.${quoteIdentifier(column)}`).join(", ")}) IS DISTINCT FROM (${others.map((column) => `EXCLUDED.${quoteIdentifier(column)}`).join(", ")})`;
|
|
4909
|
-
const lockKey = quoteLiteral(materializeLockKey(schema,
|
|
5101
|
+
const lockKey = quoteLiteral(materializeLockKey(schema, name2));
|
|
4910
5102
|
return `CREATE OR REPLACE FUNCTION ${fn}(wait boolean DEFAULT false)
|
|
4911
5103
|
RETURNS TABLE(upserted bigint, deleted bigint, locked boolean)
|
|
4912
5104
|
LANGUAGE plpgsql
|
|
@@ -4944,12 +5136,12 @@ $orm_refresh$`;
|
|
|
4944
5136
|
function refreshFunctionStatements({ namespace, previous, current }) {
|
|
4945
5137
|
const schema = quoteIdentifier(ormSchemaName(namespace));
|
|
4946
5138
|
const statements = [];
|
|
4947
|
-
for (const
|
|
4948
|
-
if (!current.has(
|
|
5139
|
+
for (const name2 of Object.keys(previous).sort()) {
|
|
5140
|
+
if (!current.has(name2)) statements.push(`DROP FUNCTION IF EXISTS ${schema}.${quoteIdentifier(materializedRefreshFunctionName(name2))}(boolean)`);
|
|
4949
5141
|
}
|
|
4950
|
-
for (const [
|
|
4951
|
-
if (previous[
|
|
4952
|
-
const fn = `${schema}.${quoteIdentifier(materializedRefreshFunctionName(
|
|
5142
|
+
for (const [name2, create] of [...current].sort(([a], [b]) => a.localeCompare(b))) {
|
|
5143
|
+
if (previous[name2] === create) continue;
|
|
5144
|
+
const fn = `${schema}.${quoteIdentifier(materializedRefreshFunctionName(name2))}(boolean)`;
|
|
4953
5145
|
statements.push(create, `REVOKE ALL ON FUNCTION ${fn} FROM PUBLIC`);
|
|
4954
5146
|
}
|
|
4955
5147
|
return statements;
|
|
@@ -4957,21 +5149,21 @@ function refreshFunctionStatements({ namespace, previous, current }) {
|
|
|
4957
5149
|
|
|
4958
5150
|
// src/engine/canonical2.ts
|
|
4959
5151
|
var FACTORIES2 = {
|
|
4960
|
-
uuid: (
|
|
4961
|
-
text: (
|
|
4962
|
-
integer: (
|
|
5152
|
+
uuid: (name2) => pgCore.uuid(name2),
|
|
5153
|
+
text: (name2) => pgCore.text(name2),
|
|
5154
|
+
integer: (name2) => pgCore.integer(name2),
|
|
4963
5155
|
// Mode 'bigint' keeps int8 lossless — mode 'number' silently rounds past
|
|
4964
5156
|
// 2^53. The read executor serializes BigInt to string for JSON.
|
|
4965
|
-
bigint: (
|
|
4966
|
-
numeric: (
|
|
4967
|
-
double: (
|
|
4968
|
-
boolean: (
|
|
4969
|
-
timestamptz: (
|
|
4970
|
-
date: (
|
|
4971
|
-
jsonb: (
|
|
5157
|
+
bigint: (name2) => pgCore.bigint(name2, { mode: "bigint" }),
|
|
5158
|
+
numeric: (name2) => pgCore.numeric(name2),
|
|
5159
|
+
double: (name2) => pgCore.doublePrecision(name2),
|
|
5160
|
+
boolean: (name2) => pgCore.boolean(name2),
|
|
5161
|
+
timestamptz: (name2) => pgCore.timestamp(name2, { withTimezone: true }),
|
|
5162
|
+
date: (name2) => pgCore.date(name2),
|
|
5163
|
+
jsonb: (name2) => pgCore.jsonb(name2)
|
|
4972
5164
|
};
|
|
4973
5165
|
var SCHEMA_IMPORT_KEY2 = "$schema";
|
|
4974
|
-
var enumImportKey = (
|
|
5166
|
+
var enumImportKey = (name2) => `$enum_${name2}`;
|
|
4975
5167
|
var columnTablesV2 = (definition) => definition.tables.filter((table) => !table.source);
|
|
4976
5168
|
function buildDrizzleEntitiesV2(definition, options = {}) {
|
|
4977
5169
|
const schemaName = ormSchemaName(definition.namespace);
|
|
@@ -5273,9 +5465,9 @@ function buildTable2(schema, table, built, enums, byName, introspection) {
|
|
|
5273
5465
|
throw new OrmDefinitionError(`table '${table.table}' declares no primaryKey`);
|
|
5274
5466
|
}
|
|
5275
5467
|
const columnNames = new Set(table.columns.map((column) => column.columnName));
|
|
5276
|
-
for (const
|
|
5277
|
-
if (!columnNames.has(
|
|
5278
|
-
throw new OrmDefinitionError(`primaryKey of '${table.table}' references unknown column '${
|
|
5468
|
+
for (const name2 of primaryKeyColumns) {
|
|
5469
|
+
if (!columnNames.has(name2)) {
|
|
5470
|
+
throw new OrmDefinitionError(`primaryKey of '${table.table}' references unknown column '${name2}'`);
|
|
5279
5471
|
}
|
|
5280
5472
|
}
|
|
5281
5473
|
const singlePrimaryKey = primaryKeyColumns.length === 1 ? primaryKeyColumns[0] : void 0;
|
|
@@ -5361,8 +5553,8 @@ function buildIndexes2(table, self) {
|
|
|
5361
5553
|
throw new OrmDefinitionError(`index on '${table.table}' references unknown column '${column}'`);
|
|
5362
5554
|
}
|
|
5363
5555
|
}
|
|
5364
|
-
const
|
|
5365
|
-
const factory = definition.unique ? pgCore.uniqueIndex(
|
|
5556
|
+
const name2 = indexName(table.table, definition);
|
|
5557
|
+
const factory = definition.unique ? pgCore.uniqueIndex(name2) : pgCore.index(name2);
|
|
5366
5558
|
const columns = definition.columns.map((column) => self[column]);
|
|
5367
5559
|
return factory.on(...columns);
|
|
5368
5560
|
});
|
|
@@ -5441,33 +5633,33 @@ function applyMigrationHintsV2(previous, definition) {
|
|
|
5441
5633
|
const snapshotTable = findTable2(adjusted, schemaName, table.table);
|
|
5442
5634
|
if (!snapshotTable) continue;
|
|
5443
5635
|
for (const column of table.columns) {
|
|
5444
|
-
const
|
|
5636
|
+
const name2 = column.columnName;
|
|
5445
5637
|
if (column.renamedFrom) {
|
|
5446
5638
|
const previousColumn = snapshotTable.columns[column.renamedFrom];
|
|
5447
|
-
if (previousColumn && snapshotTable.columns[
|
|
5639
|
+
if (previousColumn && snapshotTable.columns[name2]) {
|
|
5448
5640
|
throw new OrmDefinitionError(
|
|
5449
|
-
`'${table.table}.${
|
|
5641
|
+
`'${table.table}.${name2}' declares renamedFrom '${column.renamedFrom}', but both columns exist in the previous schema \u2014 a rename cannot overwrite a live column`
|
|
5450
5642
|
);
|
|
5451
5643
|
}
|
|
5452
5644
|
if (previousColumn) {
|
|
5453
5645
|
statements.push(
|
|
5454
|
-
`ALTER TABLE ${qualified(table.table)} RENAME COLUMN ${quoteIdentifier(column.renamedFrom)} TO ${quoteIdentifier(
|
|
5646
|
+
`ALTER TABLE ${qualified(table.table)} RENAME COLUMN ${quoteIdentifier(column.renamedFrom)} TO ${quoteIdentifier(name2)}`
|
|
5455
5647
|
);
|
|
5456
5648
|
delete snapshotTable.columns[column.renamedFrom];
|
|
5457
|
-
snapshotTable.columns[
|
|
5458
|
-
} else if (!snapshotTable.columns[
|
|
5649
|
+
snapshotTable.columns[name2] = { ...previousColumn, name: name2 };
|
|
5650
|
+
} else if (!snapshotTable.columns[name2]) {
|
|
5459
5651
|
throw new OrmDefinitionError(
|
|
5460
|
-
`'${table.table}.${
|
|
5652
|
+
`'${table.table}.${name2}' declares renamedFrom '${column.renamedFrom}', but the previous schema has neither column \u2014 check the declared source name`
|
|
5461
5653
|
);
|
|
5462
5654
|
}
|
|
5463
5655
|
}
|
|
5464
5656
|
const cast = column.cast ? castParts(column.cast) : null;
|
|
5465
5657
|
if (cast) {
|
|
5466
|
-
const current = snapshotTable.columns[
|
|
5658
|
+
const current = snapshotTable.columns[name2];
|
|
5467
5659
|
const { snapshotType, sqlType } = castTargetTypes(column, enumsByName, schemaName, table.table);
|
|
5468
5660
|
if (current && current.type !== snapshotType) {
|
|
5469
5661
|
statements.push(
|
|
5470
|
-
`ALTER TABLE ${qualified(table.table)} ALTER COLUMN ${quoteIdentifier(
|
|
5662
|
+
`ALTER TABLE ${qualified(table.table)} ALTER COLUMN ${quoteIdentifier(name2)} TYPE ${sqlType} USING ${quoteIdentifier(name2)}::${sqlType}`
|
|
5471
5663
|
);
|
|
5472
5664
|
current.type = snapshotType;
|
|
5473
5665
|
}
|
|
@@ -5478,8 +5670,8 @@ function applyMigrationHintsV2(previous, definition) {
|
|
|
5478
5670
|
}
|
|
5479
5671
|
function castTargetTypes(column, enums, schemaName, table) {
|
|
5480
5672
|
if (typeof column.type === "string") {
|
|
5481
|
-
const
|
|
5482
|
-
return { snapshotType:
|
|
5673
|
+
const name2 = sqlTypeName(column.type);
|
|
5674
|
+
return { snapshotType: name2, sqlType: name2 };
|
|
5483
5675
|
}
|
|
5484
5676
|
const enumName = column.type.enum;
|
|
5485
5677
|
if (!enums.has(enumName)) {
|
|
@@ -5564,11 +5756,11 @@ async function planWithViewRecreation({
|
|
|
5564
5756
|
})
|
|
5565
5757
|
});
|
|
5566
5758
|
const dropOrder = topologicalViewNames(
|
|
5567
|
-
new Set([...affected].filter((
|
|
5759
|
+
new Set([...affected].filter((name2) => previousViews.has(name2))),
|
|
5568
5760
|
previousDependencies
|
|
5569
5761
|
).reverse();
|
|
5570
5762
|
const createOrder = topologicalViewNames(
|
|
5571
|
-
new Set([...affected].filter((
|
|
5763
|
+
new Set([...affected].filter((name2) => currentViews.has(name2))),
|
|
5572
5764
|
currentDependencies
|
|
5573
5765
|
);
|
|
5574
5766
|
const dropDdl = await generatedViewStatements({
|
|
@@ -5585,14 +5777,14 @@ async function planWithViewRecreation({
|
|
|
5585
5777
|
});
|
|
5586
5778
|
const currentViewDefinitions = new Map((canonical.views ?? []).map((view) => [view.view, view]));
|
|
5587
5779
|
const statements = [
|
|
5588
|
-
...dropOrder.map((
|
|
5780
|
+
...dropOrder.map((name2) => ({ kind: "ddl", sql: requiredViewStatement(dropDdl, name2, schema, "DROP") })),
|
|
5589
5781
|
...hints.statements.map((statement) => ({ kind: "hint", sql: statement })),
|
|
5590
5782
|
...baseDdl.map((statement) => ({ kind: "ddl", sql: statement.replace(/;\s*$/, "") })),
|
|
5591
5783
|
...foreignKeyTimingStatements(canonical, previous, baseDdl).map((sql7) => ({ kind: "ddl", sql: sql7 })),
|
|
5592
|
-
...createOrder.map((
|
|
5593
|
-
...createOrder.map((
|
|
5594
|
-
const view = currentViewDefinitions.get(
|
|
5595
|
-
if (!view) throw new OrmDefinitionError(`missing canonical definition for view '${
|
|
5784
|
+
...createOrder.map((name2) => ({ kind: "ddl", sql: requiredViewStatement(createDdl, name2, schema, "CREATE") })),
|
|
5785
|
+
...createOrder.map((name2) => {
|
|
5786
|
+
const view = currentViewDefinitions.get(name2);
|
|
5787
|
+
if (!view) throw new OrmDefinitionError(`missing canonical definition for view '${name2}'`);
|
|
5596
5788
|
return { kind: "ddl", sql: validateViewCatalogStatement({ canonical, view, schema }) };
|
|
5597
5789
|
}),
|
|
5598
5790
|
...refreshFunctionStatements({
|
|
@@ -5622,9 +5814,9 @@ function foreignKeyTimingStatements(canonical, previous, baseDdl) {
|
|
|
5622
5814
|
}
|
|
5623
5815
|
return result;
|
|
5624
5816
|
}
|
|
5625
|
-
function foreignKeyTimingSql(schema, table,
|
|
5817
|
+
function foreignKeyTimingSql(schema, table, name2, timing) {
|
|
5626
5818
|
const clause = timing === void 0 ? "NOT DEFERRABLE" : timing === "initially-deferred" ? "DEFERRABLE INITIALLY DEFERRED" : "DEFERRABLE INITIALLY IMMEDIATE";
|
|
5627
|
-
return `ALTER TABLE ${quoteIdentifier(schema)}.${quoteIdentifier(table)} ALTER CONSTRAINT ${quoteIdentifier(
|
|
5819
|
+
return `ALTER TABLE ${quoteIdentifier(schema)}.${quoteIdentifier(table)} ALTER CONSTRAINT ${quoteIdentifier(name2)} ${clause}`;
|
|
5628
5820
|
}
|
|
5629
5821
|
function validateViewCatalogStatement({
|
|
5630
5822
|
canonical,
|
|
@@ -5766,7 +5958,7 @@ function viewReach({
|
|
|
5766
5958
|
if (viewRecreation === "all-on-base-change") return "all";
|
|
5767
5959
|
if (baseDdl.some((statement) => DROPS_TYPE.test(statement))) return "all";
|
|
5768
5960
|
const dependencies = snapshotViewDependencies(adjusted);
|
|
5769
|
-
if ([...snapshotViews(adjusted).keys()].some((
|
|
5961
|
+
if ([...snapshotViews(adjusted).keys()].some((name2) => dependencies[name2] === void 0)) return "all";
|
|
5770
5962
|
return baseChangesReachingViews({ previous, adjusted, current });
|
|
5771
5963
|
}
|
|
5772
5964
|
var SNAPSHOT_BOOKKEEPING = /* @__PURE__ */ new Set(["id", "prevId", "version", "dialect", "_meta", "views", "_sequenceOrm", "enums"]);
|
|
@@ -5815,8 +6007,8 @@ function tableChangeReachesViews({ was, is: is2 }) {
|
|
|
5815
6007
|
if (field === "columns" || VIEW_SAFE_TABLE_FIELDS.has(field)) continue;
|
|
5816
6008
|
if (stableJson(was[field]) !== stableJson(is2[field])) return true;
|
|
5817
6009
|
}
|
|
5818
|
-
return Object.entries(was.columns).some(([
|
|
5819
|
-
const next = is2.columns[
|
|
6010
|
+
return Object.entries(was.columns).some(([name2, column]) => {
|
|
6011
|
+
const next = is2.columns[name2];
|
|
5820
6012
|
if (!next) return true;
|
|
5821
6013
|
return [.../* @__PURE__ */ new Set([...Object.keys(column), ...Object.keys(next)])].some(
|
|
5822
6014
|
(field) => !VIEW_SAFE_COLUMN_FIELDS.has(field) && stableJson(column[field]) !== stableJson(next[field])
|
|
@@ -5842,22 +6034,22 @@ function affectedViewNames({
|
|
|
5842
6034
|
}) {
|
|
5843
6035
|
const allNames = /* @__PURE__ */ new Set([...previousViews.keys(), ...currentViews.keys()]);
|
|
5844
6036
|
if (reach === "all") return allNames;
|
|
5845
|
-
const dependenciesOf = (
|
|
6037
|
+
const dependenciesOf = (name2) => /* @__PURE__ */ new Set([...previousDependencies[name2] ?? [], ...currentDependencies[name2] ?? []]);
|
|
5846
6038
|
const affected = new Set(
|
|
5847
|
-
[...allNames].filter((
|
|
5848
|
-
const before = previousViews.get(
|
|
5849
|
-
const after = currentViews.get(
|
|
6039
|
+
[...allNames].filter((name2) => {
|
|
6040
|
+
const before = previousViews.get(name2);
|
|
6041
|
+
const after = currentViews.get(name2);
|
|
5850
6042
|
if (before === void 0 || after === void 0 || stableJson(before) !== stableJson(after)) return true;
|
|
5851
|
-
return [...dependenciesOf(
|
|
6043
|
+
return [...dependenciesOf(name2)].some((dependency) => reach.has(dependency));
|
|
5852
6044
|
})
|
|
5853
6045
|
);
|
|
5854
6046
|
let grew = true;
|
|
5855
6047
|
while (grew) {
|
|
5856
6048
|
grew = false;
|
|
5857
|
-
for (const
|
|
5858
|
-
if (affected.has(
|
|
5859
|
-
if ([...dependenciesOf(
|
|
5860
|
-
affected.add(
|
|
6049
|
+
for (const name2 of allNames) {
|
|
6050
|
+
if (affected.has(name2)) continue;
|
|
6051
|
+
if ([...dependenciesOf(name2)].some((dependency) => affected.has(dependency))) {
|
|
6052
|
+
affected.add(name2);
|
|
5861
6053
|
grew = true;
|
|
5862
6054
|
}
|
|
5863
6055
|
}
|
|
@@ -5868,13 +6060,13 @@ function topologicalViewNames(names, dependencies) {
|
|
|
5868
6060
|
const remaining = new Set(names);
|
|
5869
6061
|
const ordered = [];
|
|
5870
6062
|
while (remaining.size > 0) {
|
|
5871
|
-
const ready = [...remaining].filter((
|
|
6063
|
+
const ready = [...remaining].filter((name2) => (dependencies[name2] ?? []).every((dependency) => !remaining.has(dependency))).sort();
|
|
5872
6064
|
if (ready.length === 0) {
|
|
5873
6065
|
throw new OrmDefinitionError(`view dependencies form a cycle among: ${[...remaining].sort().join(", ")}`);
|
|
5874
6066
|
}
|
|
5875
|
-
for (const
|
|
5876
|
-
ordered.push(
|
|
5877
|
-
remaining.delete(
|
|
6067
|
+
for (const name2 of ready) {
|
|
6068
|
+
ordered.push(name2);
|
|
6069
|
+
remaining.delete(name2);
|
|
5878
6070
|
}
|
|
5879
6071
|
}
|
|
5880
6072
|
return ordered;
|
|
@@ -5892,18 +6084,18 @@ async function generatedViewStatements({
|
|
|
5892
6084
|
holder.views = Object.fromEntries(Object.entries(views).filter(([, view]) => !names.has(view.name)));
|
|
5893
6085
|
const generated = mode === "drop" ? await diffStatements(snapshot, without) : await diffStatements(without, snapshot);
|
|
5894
6086
|
const statements = /* @__PURE__ */ new Map();
|
|
5895
|
-
for (const
|
|
5896
|
-
const qualified = `${quoteIdentifier(schema)}.${quoteIdentifier(
|
|
6087
|
+
for (const name2 of names) {
|
|
6088
|
+
const qualified = `${quoteIdentifier(schema)}.${quoteIdentifier(name2)}`;
|
|
5897
6089
|
const prefix = mode === "drop" ? "DROP VIEW" : "CREATE VIEW";
|
|
5898
6090
|
const statement = generated.find((candidate) => candidate.trimStart().startsWith(`${prefix} ${qualified}`));
|
|
5899
|
-
if (statement) statements.set(
|
|
6091
|
+
if (statement) statements.set(name2, statement.replace(/;\s*$/, ""));
|
|
5900
6092
|
}
|
|
5901
6093
|
return statements;
|
|
5902
6094
|
}
|
|
5903
|
-
function requiredViewStatement(statements,
|
|
5904
|
-
const statement = statements.get(
|
|
6095
|
+
function requiredViewStatement(statements, name2, schema, operation) {
|
|
6096
|
+
const statement = statements.get(name2);
|
|
5905
6097
|
if (!statement) {
|
|
5906
|
-
throw new OrmDefinitionError(`drizzle-kit did not generate ${operation} VIEW for '${schema}.${
|
|
6098
|
+
throw new OrmDefinitionError(`drizzle-kit did not generate ${operation} VIEW for '${schema}.${name2}'`);
|
|
5907
6099
|
}
|
|
5908
6100
|
return statement;
|
|
5909
6101
|
}
|
|
@@ -6149,9 +6341,13 @@ function renderOrmMigrationExport(exported) {
|
|
|
6149
6341
|
if (Buffer.byteLength(text4, "utf8") > ORM_MIGRATION_EXPORT_MAX_BYTES) throw new Error("ORM migration export exceeds the size limit");
|
|
6150
6342
|
return text4;
|
|
6151
6343
|
}
|
|
6152
|
-
var IR_VERSION =
|
|
6344
|
+
var IR_VERSION = 5;
|
|
6153
6345
|
var LEGACY_IR_VERSION = 1;
|
|
6154
|
-
var
|
|
6346
|
+
var GUARDED_IR_VERSION = 2;
|
|
6347
|
+
var COMPOSED_IR_VERSION = 3;
|
|
6348
|
+
var SQL_IR_VERSION = 4;
|
|
6349
|
+
var RUNTIME_SQL_IR_VERSION = 5;
|
|
6350
|
+
var irVersionSchema = zod.z.union([zod.z.literal(LEGACY_IR_VERSION), zod.z.literal(GUARDED_IR_VERSION), zod.z.literal(COMPOSED_IR_VERSION), zod.z.literal(SQL_IR_VERSION), zod.z.literal(RUNTIME_SQL_IR_VERSION)]);
|
|
6155
6351
|
var operationVersionSchema = zod.z.object({ irVersion: irVersionSchema });
|
|
6156
6352
|
function requiredArtifactIrVersion(operations) {
|
|
6157
6353
|
let required = LEGACY_IR_VERSION;
|
|
@@ -6203,8 +6399,8 @@ var predIrSchema = zod.z.lazy(
|
|
|
6203
6399
|
value: zod.z.number(),
|
|
6204
6400
|
where: predIrSchema.optional()
|
|
6205
6401
|
}),
|
|
6206
|
-
zod.z.object({ kind: zod.z.literal("capability"), key: zod.z.string() }),
|
|
6207
|
-
zod.z.object({ kind: zod.z.literal("role"), key: zod.z.string() }),
|
|
6402
|
+
zod.z.object({ kind: zod.z.literal("capability"), key: zod.z.string(), namespace: zod.z.string().optional() }),
|
|
6403
|
+
zod.z.object({ kind: zod.z.literal("role"), key: zod.z.string(), namespace: zod.z.string().optional() }),
|
|
6208
6404
|
zod.z.object({ kind: zod.z.literal("dynamicFilter"), slot: zod.z.string() }),
|
|
6209
6405
|
zod.z.object({ kind: zod.z.literal("const"), value: zod.z.boolean() })
|
|
6210
6406
|
])
|
|
@@ -6431,7 +6627,31 @@ var variableIrSchema = zod.z.object({
|
|
|
6431
6627
|
/** Absent = no default declared; see {@link variableDefaultIrSchema}. */
|
|
6432
6628
|
defaultValue: variableDefaultIrSchema.optional()
|
|
6433
6629
|
});
|
|
6630
|
+
var sqlColumnSchema = zod.z.object({ name: zod.z.string(), type: zod.z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]), nullable: zod.z.boolean() });
|
|
6434
6631
|
var compiledOperationSchema = zod.z.object({
|
|
6632
|
+
language: zod.z.literal("sql").optional(),
|
|
6633
|
+
/** Server-compiled object catalog and permission ceiling, never caller SQL. */
|
|
6634
|
+
sqlQuery: zod.z.object({
|
|
6635
|
+
catalog: zod.z.object({
|
|
6636
|
+
identity: zod.z.string(),
|
|
6637
|
+
objects: zod.z.array(zod.z.object({
|
|
6638
|
+
ref: zod.z.string(),
|
|
6639
|
+
table: zod.z.string(),
|
|
6640
|
+
columns: zod.z.array(sqlColumnSchema.extend({ type: zod.z.string() })).min(1),
|
|
6641
|
+
scan: zod.z.string().min(1),
|
|
6642
|
+
read: zod.z.array(zod.z.string()).min(1)
|
|
6643
|
+
}).strict()).min(1).max(256)
|
|
6644
|
+
}).strict(),
|
|
6645
|
+
read: zod.z.array(zod.z.string()).min(1).max(256),
|
|
6646
|
+
maxRows: zod.z.number().int().min(1).max(1e4)
|
|
6647
|
+
}).strict().optional(),
|
|
6648
|
+
sql: zod.z.object({
|
|
6649
|
+
text: zod.z.string().min(1).max(1048576),
|
|
6650
|
+
columns: zod.z.array(sqlColumnSchema).min(1).max(256),
|
|
6651
|
+
parameters: zod.z.array(zod.z.string()).max(128),
|
|
6652
|
+
read: zod.z.array(zod.z.string()).min(1).max(256),
|
|
6653
|
+
maxRows: zod.z.number().int().min(1).max(1e4)
|
|
6654
|
+
}).strict().optional(),
|
|
6435
6655
|
irVersion: irVersionSchema,
|
|
6436
6656
|
name: zod.z.string(),
|
|
6437
6657
|
opHash: zod.z.string().regex(/^sha256:[0-9a-f]{64}$/),
|
|
@@ -6443,8 +6663,16 @@ var compiledOperationSchema = zod.z.object({
|
|
|
6443
6663
|
variables: zod.z.array(variableIrSchema),
|
|
6444
6664
|
/** Every table the op touches — data-token scoping + audit. */
|
|
6445
6665
|
tableSet: zod.z.array(zod.z.string()).min(1),
|
|
6446
|
-
roots: zod.z.array(rootIrSchema)
|
|
6666
|
+
roots: zod.z.array(rootIrSchema)
|
|
6447
6667
|
}).superRefine((operation, ctx) => {
|
|
6668
|
+
if (operation.language === "sql") {
|
|
6669
|
+
const validPlan = operation.sqlQuery ? !operation.sql && operation.irVersion === RUNTIME_SQL_IR_VERSION : !!operation.sql && operation.irVersion === SQL_IR_VERSION;
|
|
6670
|
+
if (!validPlan || operation.kind !== "query" || operation.roots.length !== 0 || !operation.requires?.capability) {
|
|
6671
|
+
ctx.addIssue({ code: "custom", message: "SQL operations require a query-only v4 plan or v5 runtime contract, capability, and no GraphQL roots" });
|
|
6672
|
+
}
|
|
6673
|
+
} else if (operation.sql || operation.sqlQuery || operation.roots.length === 0) {
|
|
6674
|
+
ctx.addIssue({ code: "custom", message: "GraphQL operations require roots and cannot carry SQL" });
|
|
6675
|
+
}
|
|
6448
6676
|
for (const [index3, root] of operation.roots.entries()) {
|
|
6449
6677
|
if (root.kind !== "mutation" || root.assertAffectedRows === void 0) continue;
|
|
6450
6678
|
if (operation.irVersion < 2 || operation.kind !== "mutation" || root.includeIf !== void 0) {
|
|
@@ -6491,11 +6719,16 @@ var resolvedMetaIrSchema = zod.z.object({
|
|
|
6491
6719
|
});
|
|
6492
6720
|
var tableMetaIrSchema = zod.z.object({
|
|
6493
6721
|
table: zod.z.string(),
|
|
6722
|
+
namespace: zod.z.string().optional(),
|
|
6723
|
+
dependencies: zod.z.array(zod.z.string()).optional(),
|
|
6724
|
+
foreignReferences: zod.z.array(zod.z.object({ column: zod.z.string(), target: zod.z.string(), targetColumn: zod.z.string() })).optional(),
|
|
6725
|
+
exportRead: zod.z.union([zod.z.literal(true), zod.z.object({ capability: zod.z.string() }), zod.z.object({ role: zod.z.string() })]).optional(),
|
|
6726
|
+
exportWrite: zod.z.union([zod.z.literal(true), zod.z.object({ capability: zod.z.string() }), zod.z.object({ role: zod.z.string() })]).optional(),
|
|
6494
6727
|
schema: zod.z.string(),
|
|
6495
6728
|
typename: zod.z.string(),
|
|
6496
6729
|
/**
|
|
6497
6730
|
* True when statements over this relation evaluate a claims-gated synced
|
|
6498
|
-
* view: synced twins (`source:
|
|
6731
|
+
* view: synced twins (`source: schema.table`) materialize as views whose baked
|
|
6499
6732
|
* policy reads `current_setting('request.jwt.claims')`, and namespace views
|
|
6500
6733
|
* inherit the gate transitively through their declared dependencies. The
|
|
6501
6734
|
* executor must stamp that GUC (transaction-scoped) before any statement
|
|
@@ -6554,13 +6787,21 @@ var compiledArtifactSchema = zod.z.object({
|
|
|
6554
6787
|
irVersion: irVersionSchema,
|
|
6555
6788
|
namespace: zod.z.string(),
|
|
6556
6789
|
schemaHash: zod.z.string(),
|
|
6790
|
+
ownSchemaHash: zod.z.string().optional(),
|
|
6791
|
+
dependencies: zod.z.record(zod.z.string(), zod.z.string()).optional(),
|
|
6792
|
+
readPolicies: zod.z.record(zod.z.string(), predIrSchema).optional(),
|
|
6557
6793
|
opsHash: zod.z.string(),
|
|
6794
|
+
/** Apply-observed synced objects without a serving backing; not part of the authored schema hash. */
|
|
6795
|
+
unbackedTables: zod.z.array(zod.z.string()).optional(),
|
|
6558
6796
|
tables: zod.z.record(zod.z.string(), tableMetaIrSchema),
|
|
6559
6797
|
operations: zod.z.record(zod.z.string(), compiledOperationSchema)
|
|
6560
6798
|
}).superRefine((artifact, ctx) => {
|
|
6561
|
-
|
|
6799
|
+
if (Object.keys(artifact.dependencies ?? {}).length > 0 && artifact.irVersion < COMPOSED_IR_VERSION) {
|
|
6800
|
+
ctx.addIssue({ code: "custom", path: ["irVersion"], message: "cross-namespace dependencies require IR v3" });
|
|
6801
|
+
}
|
|
6802
|
+
for (const [name2, operation] of Object.entries(artifact.operations)) {
|
|
6562
6803
|
if (operation.irVersion > artifact.irVersion) {
|
|
6563
|
-
ctx.addIssue({ code: "custom", path: ["operations",
|
|
6804
|
+
ctx.addIssue({ code: "custom", path: ["operations", name2, "irVersion"], message: "operation requires a newer IR version than its artifact" });
|
|
6564
6805
|
}
|
|
6565
6806
|
}
|
|
6566
6807
|
});
|
|
@@ -6573,7 +6814,7 @@ var IrVersionError = class extends Error {
|
|
|
6573
6814
|
function normalizeIr(raw) {
|
|
6574
6815
|
if (!raw || typeof raw !== "object") throw new IrVersionError(void 0);
|
|
6575
6816
|
const version = raw.irVersion;
|
|
6576
|
-
if (version
|
|
6817
|
+
if (!irVersionSchema.safeParse(version).success) throw new IrVersionError(version);
|
|
6577
6818
|
return compiledArtifactSchema.parse(raw);
|
|
6578
6819
|
}
|
|
6579
6820
|
function indexByHash(artifact) {
|
|
@@ -6606,10 +6847,10 @@ function buildNamespaceDefinition(files, config) {
|
|
|
6606
6847
|
}
|
|
6607
6848
|
const actionsSeen = /* @__PURE__ */ new Map();
|
|
6608
6849
|
for (const { file, table } of files) {
|
|
6609
|
-
for (const
|
|
6610
|
-
const existing = actionsSeen.get(
|
|
6611
|
-
if (existing) throw new OrmDefinitionError(`action '${
|
|
6612
|
-
actionsSeen.set(
|
|
6850
|
+
for (const name2 of Object.keys(table.actions ?? {})) {
|
|
6851
|
+
const existing = actionsSeen.get(name2);
|
|
6852
|
+
if (existing) throw new OrmDefinitionError(`action '${name2}' is already defined in ${existing}`, file);
|
|
6853
|
+
actionsSeen.set(name2, file);
|
|
6613
6854
|
}
|
|
6614
6855
|
}
|
|
6615
6856
|
const capabilities = config?.capabilities ? { roles: config.capabilities.roles ?? EMPTY_CAPABILITIES.roles, uses: config.capabilities.uses ?? [] } : EMPTY_CAPABILITIES;
|
|
@@ -6652,12 +6893,12 @@ function validateActionValidates(files, byName) {
|
|
|
6652
6893
|
}
|
|
6653
6894
|
function validateIndexNames(files) {
|
|
6654
6895
|
const seen = /* @__PURE__ */ new Map();
|
|
6655
|
-
const claim = (
|
|
6656
|
-
const existing = seen.get(
|
|
6896
|
+
const claim = (name2, label, file) => {
|
|
6897
|
+
const existing = seen.get(name2);
|
|
6657
6898
|
if (existing) {
|
|
6658
|
-
throw new OrmDefinitionError(`relation name '${
|
|
6899
|
+
throw new OrmDefinitionError(`relation name '${name2}' (${label}) collides with ${existing}`, file);
|
|
6659
6900
|
}
|
|
6660
|
-
seen.set(
|
|
6901
|
+
seen.set(name2, `${label} (${file})`);
|
|
6661
6902
|
};
|
|
6662
6903
|
for (const { file, table } of files) {
|
|
6663
6904
|
claim(table.table, `table '${table.table}'`, file);
|
|
@@ -6665,9 +6906,9 @@ function validateIndexNames(files) {
|
|
|
6665
6906
|
for (const { file, table } of files) {
|
|
6666
6907
|
if (table.source) continue;
|
|
6667
6908
|
claim(`${table.table}_pkey`, `implicit primary-key index of '${table.table}'`, file);
|
|
6668
|
-
for (const [
|
|
6909
|
+
for (const [name2, column] of Object.entries(table.columns ?? {})) {
|
|
6669
6910
|
if (column.unique && !column.primary_key) {
|
|
6670
|
-
claim(`${table.table}_${
|
|
6911
|
+
claim(`${table.table}_${name2}_unique`, `implicit unique constraint of '${table.table}.${name2}'`, file);
|
|
6671
6912
|
}
|
|
6672
6913
|
}
|
|
6673
6914
|
}
|
|
@@ -6679,20 +6920,20 @@ function validateIndexNames(files) {
|
|
|
6679
6920
|
}
|
|
6680
6921
|
function validateLocalLinks(files, byName) {
|
|
6681
6922
|
for (const { file, table } of files) {
|
|
6682
|
-
for (const [
|
|
6923
|
+
for (const [name2, link] of Object.entries(table.links ?? {})) {
|
|
6683
6924
|
if (isQualifiedReference(link.to)) continue;
|
|
6684
6925
|
const target = byName.get(link.to);
|
|
6685
6926
|
if (!target) {
|
|
6686
|
-
throw new OrmDefinitionError(`link '${
|
|
6927
|
+
throw new OrmDefinitionError(`link '${name2}' references unknown table '${link.to}'`, file);
|
|
6687
6928
|
}
|
|
6688
6929
|
if (link.enforce && target.source) {
|
|
6689
|
-
throw new OrmDefinitionError(`link '${
|
|
6930
|
+
throw new OrmDefinitionError(`link '${name2}' cannot enforce a foreign key onto a synced table`, file);
|
|
6690
6931
|
}
|
|
6691
6932
|
const sourceType = table.columns?.[link.on]?.type;
|
|
6692
6933
|
const targetPrimaryKey = Object.values(target.columns ?? {}).find((column) => column.primary_key);
|
|
6693
6934
|
if (sourceType && targetPrimaryKey && columnTypeGroup(sourceType) !== columnTypeGroup(targetPrimaryKey.type)) {
|
|
6694
6935
|
throw new OrmDefinitionError(
|
|
6695
|
-
`link '${
|
|
6936
|
+
`link '${name2}' can never join: '${link.on}' is ${sourceType} but '${link.to}' has a ${targetPrimaryKey.type} primary key`,
|
|
6696
6937
|
file
|
|
6697
6938
|
);
|
|
6698
6939
|
}
|
|
@@ -6772,7 +7013,7 @@ function parseYaml(file, contents) {
|
|
|
6772
7013
|
throw new OrmDefinitionError(error instanceof Error ? error.message : "invalid YAML", file);
|
|
6773
7014
|
}
|
|
6774
7015
|
}
|
|
6775
|
-
var isDefinitionFile = (
|
|
7016
|
+
var isDefinitionFile = (name2) => DEFINITION_FILE_EXTENSIONS.some((extension) => path.extname(name2) === extension);
|
|
6776
7017
|
async function listDefinitionFiles(dir) {
|
|
6777
7018
|
const entries = await promises.readdir(dir, { withFileTypes: true });
|
|
6778
7019
|
return entries.filter((entry) => entry.isFile() && isDefinitionFile(entry.name)).map((entry) => entry.name).sort();
|
|
@@ -6866,13 +7107,13 @@ async function readMigrationSql(dir, entry) {
|
|
|
6866
7107
|
var checksumOf = (text4) => crypto.createHash("sha256").update(text4).digest("hex");
|
|
6867
7108
|
var migrationName = (ordinal, contentHash) => `${pad(ordinal)}_${contentHash.slice(0, 12)}`;
|
|
6868
7109
|
function renderMigrationSql({
|
|
6869
|
-
name,
|
|
7110
|
+
name: name2,
|
|
6870
7111
|
namespace,
|
|
6871
7112
|
contentHash,
|
|
6872
7113
|
plan
|
|
6873
7114
|
}) {
|
|
6874
7115
|
const header = [
|
|
6875
|
-
`-- ${
|
|
7116
|
+
`-- ${name2}`,
|
|
6876
7117
|
`-- namespace: ${namespace}`,
|
|
6877
7118
|
`-- content hash: ${contentHash}`,
|
|
6878
7119
|
"-- generated by `seq-studio orm diff` \u2014 review and commit, do not edit"
|
|
@@ -6888,13 +7129,13 @@ async function writeMigration({
|
|
|
6888
7129
|
}) {
|
|
6889
7130
|
const tip = workspace.journal.entries.at(-1);
|
|
6890
7131
|
const ordinal = (tip?.ordinal ?? 0) + 1;
|
|
6891
|
-
const
|
|
6892
|
-
const sql7 = renderMigrationSql({ name, namespace: workspace.journal.namespace, contentHash, plan });
|
|
7132
|
+
const name2 = migrationName(ordinal, contentHash);
|
|
7133
|
+
const sql7 = renderMigrationSql({ name: name2, namespace: workspace.journal.namespace, contentHash, plan });
|
|
6893
7134
|
const metaDir = path.join(dir, MIGRATIONS_DIRNAME, META_DIRNAME);
|
|
6894
7135
|
await promises.mkdir(metaDir, { recursive: true });
|
|
6895
7136
|
const snapshotText = `${JSON.stringify(plan.snapshot, null, 2)}
|
|
6896
7137
|
`;
|
|
6897
|
-
await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${
|
|
7138
|
+
await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${name2}.sql`), sql7);
|
|
6898
7139
|
await promises.writeFile(path.join(metaDir, `${pad(ordinal)}.snapshot.json`), snapshotText);
|
|
6899
7140
|
const journal = {
|
|
6900
7141
|
...workspace.journal,
|
|
@@ -6902,7 +7143,7 @@ async function writeMigration({
|
|
|
6902
7143
|
...workspace.journal.entries,
|
|
6903
7144
|
{
|
|
6904
7145
|
ordinal,
|
|
6905
|
-
name,
|
|
7146
|
+
name: name2,
|
|
6906
7147
|
content_hash: contentHash,
|
|
6907
7148
|
previous_content_hash: tip?.content_hash ?? null,
|
|
6908
7149
|
checksum: checksumOf(sql7),
|
|
@@ -6913,7 +7154,7 @@ async function writeMigration({
|
|
|
6913
7154
|
};
|
|
6914
7155
|
await promises.writeFile(path.join(metaDir, JOURNAL_FILENAME), `${JSON.stringify(journal, null, 2)}
|
|
6915
7156
|
`);
|
|
6916
|
-
return { name, ordinal, destructive };
|
|
7157
|
+
return { name: name2, ordinal, destructive };
|
|
6917
7158
|
}
|
|
6918
7159
|
var pad = (ordinal) => String(ordinal).padStart(4, "0");
|
|
6919
7160
|
function parseJson(path, raw) {
|
|
@@ -7087,15 +7328,15 @@ function convertV1DefinitionToCanonical(definition) {
|
|
|
7087
7328
|
}
|
|
7088
7329
|
function convertTable(table) {
|
|
7089
7330
|
const columns = Object.entries(table.columns ?? {});
|
|
7090
|
-
const primaryKey2 = columns.filter(([, column]) => column.primary_key).map(([
|
|
7331
|
+
const primaryKey2 = columns.filter(([, column]) => column.primary_key).map(([name2]) => name2);
|
|
7091
7332
|
return {
|
|
7092
7333
|
table: table.table,
|
|
7093
7334
|
description: table.description,
|
|
7094
7335
|
source: table.source,
|
|
7095
|
-
columns: columns.map(([
|
|
7336
|
+
columns: columns.map(([name2, column]) => convertColumn(name2, column)),
|
|
7096
7337
|
// Synced tables may declare a typed column/PK contract; untyped remain empty.
|
|
7097
7338
|
primaryKey: primaryKey2.length > 0 ? primaryKey2 : void 0,
|
|
7098
|
-
links: Object.entries(table.links ?? {}).map(([
|
|
7339
|
+
links: Object.entries(table.links ?? {}).map(([name2, link]) => convertLink(table.table, name2, link)),
|
|
7099
7340
|
indexes: (table.indexes ?? []).map((index3) => ({
|
|
7100
7341
|
columns: [...index3.columns],
|
|
7101
7342
|
unique: index3.unique ?? false,
|
|
@@ -7112,11 +7353,11 @@ function convertTable(table) {
|
|
|
7112
7353
|
actions: []
|
|
7113
7354
|
};
|
|
7114
7355
|
}
|
|
7115
|
-
function convertColumn(
|
|
7356
|
+
function convertColumn(name2, column) {
|
|
7116
7357
|
const isDefaultFn = column.default !== void 0 && isFunctionDefault(column.default, column.type);
|
|
7117
7358
|
return {
|
|
7118
|
-
name,
|
|
7119
|
-
columnName:
|
|
7359
|
+
name: name2,
|
|
7360
|
+
columnName: name2,
|
|
7120
7361
|
type: column.type,
|
|
7121
7362
|
nullable: column.primary_key ? false : column.nullable !== false,
|
|
7122
7363
|
unique: column.unique === true,
|
|
@@ -7130,14 +7371,14 @@ function convertColumn(name, column) {
|
|
|
7130
7371
|
deprecated: column.deprecated === true
|
|
7131
7372
|
};
|
|
7132
7373
|
}
|
|
7133
|
-
function convertLink(table,
|
|
7374
|
+
function convertLink(table, name2, link) {
|
|
7134
7375
|
if (link.to.includes(".")) {
|
|
7135
7376
|
throw new OrmDefinitionError(
|
|
7136
|
-
`link '${
|
|
7377
|
+
`link '${name2}' on '${table}' targets '${link.to}' \u2014 cross-namespace links have no canonical v2 representation (links are namespace-local in v2)`
|
|
7137
7378
|
);
|
|
7138
7379
|
}
|
|
7139
7380
|
return {
|
|
7140
|
-
name,
|
|
7381
|
+
name: name2,
|
|
7141
7382
|
kind: "toOne",
|
|
7142
7383
|
target: link.to,
|
|
7143
7384
|
on: link.on,
|
|
@@ -7166,29 +7407,29 @@ function buildRebaseJournalEntry({ namespace, previousHash, newHash, ordinal, sn
|
|
|
7166
7407
|
if (!Number.isInteger(ordinal) || ordinal < 2) {
|
|
7167
7408
|
throw new OrmDefinitionError(`rebase ordinal must be an integer \u2265 2 (a rebase sits on an existing journal tip); got ${ordinal}`);
|
|
7168
7409
|
}
|
|
7169
|
-
const
|
|
7170
|
-
const sql7 = renderRebaseMigrationSql({ name, namespace, previousHash, newHash });
|
|
7410
|
+
const name2 = migrationName(ordinal, newHash);
|
|
7411
|
+
const sql7 = renderRebaseMigrationSql({ name: name2, namespace, previousHash, newHash });
|
|
7171
7412
|
const snapshotText = `${JSON.stringify(snapshot, null, 2)}
|
|
7172
7413
|
`;
|
|
7173
7414
|
const entry = {
|
|
7174
7415
|
ordinal,
|
|
7175
|
-
name,
|
|
7416
|
+
name: name2,
|
|
7176
7417
|
content_hash: newHash,
|
|
7177
7418
|
previous_content_hash: previousHash,
|
|
7178
7419
|
checksum: checksumOf(sql7),
|
|
7179
7420
|
snapshot_checksum: checksumOf(snapshotText),
|
|
7180
7421
|
destructive: false
|
|
7181
7422
|
};
|
|
7182
|
-
return { name, entry, sql: sql7, snapshotText };
|
|
7423
|
+
return { name: name2, entry, sql: sql7, snapshotText };
|
|
7183
7424
|
}
|
|
7184
7425
|
function renderRebaseMigrationSql({
|
|
7185
|
-
name,
|
|
7426
|
+
name: name2,
|
|
7186
7427
|
namespace,
|
|
7187
7428
|
previousHash,
|
|
7188
7429
|
newHash
|
|
7189
7430
|
}) {
|
|
7190
7431
|
return [
|
|
7191
|
-
`-- ${
|
|
7432
|
+
`-- ${name2}`,
|
|
7192
7433
|
`-- namespace: ${namespace}`,
|
|
7193
7434
|
`-- content hash: ${newHash}`,
|
|
7194
7435
|
`-- rebase: v1 definitions (${previousHash}) converted to canonical v2 \u2014 verified no-op, zero statements`,
|
|
@@ -7262,8 +7503,8 @@ function canonicalActions(value) {
|
|
|
7262
7503
|
const entries = Object.entries(value);
|
|
7263
7504
|
if (entries.length === 0) return void 0;
|
|
7264
7505
|
return Object.fromEntries(
|
|
7265
|
-
entries.map(([
|
|
7266
|
-
|
|
7506
|
+
entries.map(([name2, action]) => [
|
|
7507
|
+
name2,
|
|
7267
7508
|
{
|
|
7268
7509
|
description: action["description"] ?? void 0,
|
|
7269
7510
|
requires: action["requires"] ?? void 0,
|
|
@@ -7276,6 +7517,526 @@ function canonicalActions(value) {
|
|
|
7276
7517
|
])
|
|
7277
7518
|
);
|
|
7278
7519
|
}
|
|
7520
|
+
var IMPORT_CONFLICT_MODES = ["error", "skip", "update"];
|
|
7521
|
+
var columnMapSchema = zod.z.object({
|
|
7522
|
+
/** ORM property name or Postgres column name. */
|
|
7523
|
+
target: zod.z.string().min(1).max(63),
|
|
7524
|
+
/** Column on the source relation. Identifiers only — never an expression. */
|
|
7525
|
+
source: zod.z.string().min(1).max(63),
|
|
7526
|
+
/**
|
|
7527
|
+
* Used when the source value is NULL. Required columns without a database
|
|
7528
|
+
* default fail the load if the source contains nulls and this is omitted.
|
|
7529
|
+
*/
|
|
7530
|
+
nullDefault: zod.z.union([zod.z.string().max(1e4), zod.z.number(), zod.z.boolean()]).optional()
|
|
7531
|
+
}).strict();
|
|
7532
|
+
var importSpecSchema = zod.z.object({
|
|
7533
|
+
table: zod.z.string().min(1).max(63),
|
|
7534
|
+
from: zod.z.string().regex(QUALIFIED_TABLE_PATTERN),
|
|
7535
|
+
columns: zod.z.array(columnMapSchema).min(1).max(500),
|
|
7536
|
+
onConflict: zod.z.enum(IMPORT_CONFLICT_MODES).default("skip"),
|
|
7537
|
+
batchSize: zod.z.number().int().min(1).max(2e4).default(5e3)
|
|
7538
|
+
}).strict();
|
|
7539
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7540
|
+
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
7541
|
+
var TIMESTAMPTZ_RE = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$/;
|
|
7542
|
+
var INTEGER_RE = /^-?\d+$/;
|
|
7543
|
+
var NUMERIC_RE = /^-?\d+(\.\d+)?$/;
|
|
7544
|
+
function sourceSchemaAllowed(schema) {
|
|
7545
|
+
return schema !== "information_schema" && !schema.startsWith("pg_");
|
|
7546
|
+
}
|
|
7547
|
+
function isRelationIdentifier(value) {
|
|
7548
|
+
return value.length <= 63 && IDENTIFIER_PATTERN.test(value);
|
|
7549
|
+
}
|
|
7550
|
+
function isSourceColumn(value) {
|
|
7551
|
+
return value.length <= 63 && COLUMN_IDENTIFIER_PATTERN.test(value);
|
|
7552
|
+
}
|
|
7553
|
+
function splitQualifiedRelation(from) {
|
|
7554
|
+
const dot = from.indexOf(".");
|
|
7555
|
+
if (dot <= 0 || dot !== from.lastIndexOf(".")) return null;
|
|
7556
|
+
const schema = from.slice(0, dot);
|
|
7557
|
+
const table = from.slice(dot + 1);
|
|
7558
|
+
if (!isRelationIdentifier(schema) || !isRelationIdentifier(table)) return null;
|
|
7559
|
+
return { schema, table };
|
|
7560
|
+
}
|
|
7561
|
+
function postgresTypeName(type) {
|
|
7562
|
+
if (type === "double") return "double precision";
|
|
7563
|
+
if (type === "timestamptz") return "timestamp with time zone";
|
|
7564
|
+
return type;
|
|
7565
|
+
}
|
|
7566
|
+
function columnPostgresType(column, schemaName, enums) {
|
|
7567
|
+
if (typeof column.type === "string") return postgresTypeName(column.type);
|
|
7568
|
+
if (!enums.has(column.type.enum)) return null;
|
|
7569
|
+
return `${quoteIdentifier(schemaName)}.${quoteIdentifier(column.type.enum)}`;
|
|
7570
|
+
}
|
|
7571
|
+
function quoteQualified(schema, table) {
|
|
7572
|
+
return `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
|
|
7573
|
+
}
|
|
7574
|
+
var INT4_MIN = -2147483648;
|
|
7575
|
+
var INT4_MAX = 2147483647;
|
|
7576
|
+
function resumeLiteralSql({
|
|
7577
|
+
column,
|
|
7578
|
+
after,
|
|
7579
|
+
schemaName,
|
|
7580
|
+
enums
|
|
7581
|
+
}) {
|
|
7582
|
+
const coerced = coerceResumeValue(column, after);
|
|
7583
|
+
if (!coerced.ok) return coerced;
|
|
7584
|
+
return literalSql({ column, value: coerced.value, schemaName, enums });
|
|
7585
|
+
}
|
|
7586
|
+
function coerceResumeValue(column, after) {
|
|
7587
|
+
const type = typeof column.type === "string" ? column.type : null;
|
|
7588
|
+
if (type === "integer") {
|
|
7589
|
+
if (!INTEGER_RE.test(after)) return { ok: false, error: `${column.name}: after must be an integer` };
|
|
7590
|
+
const parsed = Number(after);
|
|
7591
|
+
if (!Number.isSafeInteger(parsed) || parsed < INT4_MIN || parsed > INT4_MAX) {
|
|
7592
|
+
return { ok: false, error: `${column.name}: after must be an integer` };
|
|
7593
|
+
}
|
|
7594
|
+
return { ok: true, value: parsed };
|
|
7595
|
+
}
|
|
7596
|
+
if (type === "boolean") {
|
|
7597
|
+
if (after !== "true" && after !== "false") return { ok: false, error: `${column.name}: after must be true or false` };
|
|
7598
|
+
return { ok: true, value: after === "true" };
|
|
7599
|
+
}
|
|
7600
|
+
return { ok: true, value: after };
|
|
7601
|
+
}
|
|
7602
|
+
function literalSql({
|
|
7603
|
+
column,
|
|
7604
|
+
value,
|
|
7605
|
+
schemaName,
|
|
7606
|
+
enums
|
|
7607
|
+
}) {
|
|
7608
|
+
const label = column.name;
|
|
7609
|
+
if (typeof value === "string" && value.includes("\0")) {
|
|
7610
|
+
return { ok: false, error: `${label}: null default contains a NUL character` };
|
|
7611
|
+
}
|
|
7612
|
+
if (typeof column.type !== "string") {
|
|
7613
|
+
const enumDef = enums.get(column.type.enum);
|
|
7614
|
+
if (!enumDef) return { ok: false, error: `${label}: enum '${column.type.enum}' is not declared` };
|
|
7615
|
+
if (typeof value !== "string" || !enumDef.values.includes(value)) {
|
|
7616
|
+
return { ok: false, error: `${label}: null default must be one of ${enumDef.values.join(", ")}` };
|
|
7617
|
+
}
|
|
7618
|
+
const typeName = columnPostgresType(column, schemaName, enums);
|
|
7619
|
+
return { ok: true, sql: `${quoteLiteral(value)}::${typeName}` };
|
|
7620
|
+
}
|
|
7621
|
+
return scalarLiteral2({ column, type: column.type, value, label });
|
|
7622
|
+
}
|
|
7623
|
+
var SCALAR_LITERALS = {
|
|
7624
|
+
text: (value, label) => typeof value === "string" ? { ok: true, sql: quoteLiteral(value) } : { ok: false, error: `${label}: null default must be a string` },
|
|
7625
|
+
uuid: (value, label) => typeof value === "string" && UUID_RE.test(value) ? { ok: true, sql: `${quoteLiteral(value)}::uuid` } : { ok: false, error: `${label}: null default must be a UUID` },
|
|
7626
|
+
boolean: (value, label) => typeof value === "boolean" ? { ok: true, sql: value ? "TRUE" : "FALSE" } : { ok: false, error: `${label}: null default must be true or false` },
|
|
7627
|
+
integer: (value, label) => typeof value === "number" && Number.isSafeInteger(value) ? { ok: true, sql: String(value) } : { ok: false, error: `${label}: null default must be an integer` },
|
|
7628
|
+
bigint: (value, label) => typeof value === "string" && INTEGER_RE.test(value) ? { ok: true, sql: `${value}::bigint` } : { ok: false, error: `${label}: null default must be a base-10 integer string` },
|
|
7629
|
+
numeric: numericLiteral,
|
|
7630
|
+
double: numericLiteral,
|
|
7631
|
+
date: (value, label) => typeof value === "string" && DATE_RE.test(value) ? { ok: true, sql: `${quoteLiteral(value)}::date` } : { ok: false, error: `${label}: null default must be a YYYY-MM-DD date` },
|
|
7632
|
+
timestamptz: (value, label) => typeof value === "string" && TIMESTAMPTZ_RE.test(value) ? { ok: true, sql: `${quoteLiteral(value)}::${postgresTypeName("timestamptz")}` } : { ok: false, error: `${label}: null default must be an ISO-8601 timestamp` },
|
|
7633
|
+
jsonb: jsonbLiteral
|
|
7634
|
+
};
|
|
7635
|
+
function numericLiteral(value, label) {
|
|
7636
|
+
if (typeof value === "number" && Number.isFinite(value)) return { ok: true, sql: String(value) };
|
|
7637
|
+
if (typeof value === "string" && NUMERIC_RE.test(value)) return { ok: true, sql: value };
|
|
7638
|
+
return { ok: false, error: `${label}: null default must be a finite number` };
|
|
7639
|
+
}
|
|
7640
|
+
function scalarLiteral2({
|
|
7641
|
+
column,
|
|
7642
|
+
type,
|
|
7643
|
+
value,
|
|
7644
|
+
label
|
|
7645
|
+
}) {
|
|
7646
|
+
const encode = SCALAR_LITERALS[type];
|
|
7647
|
+
if (!encode) return { ok: false, error: `${column.name}: unsupported type '${type}'` };
|
|
7648
|
+
return encode(value, label);
|
|
7649
|
+
}
|
|
7650
|
+
function jsonbLiteral(value, label) {
|
|
7651
|
+
if (typeof value !== "string") return { ok: false, error: `${label}: null default must be a JSON string` };
|
|
7652
|
+
try {
|
|
7653
|
+
JSON.parse(value);
|
|
7654
|
+
} catch {
|
|
7655
|
+
return { ok: false, error: `${label}: null default must be valid JSON` };
|
|
7656
|
+
}
|
|
7657
|
+
return { ok: true, sql: `${quoteLiteral(value)}::jsonb` };
|
|
7658
|
+
}
|
|
7659
|
+
function dollarQuote(body) {
|
|
7660
|
+
const digest = crypto.createHash("sha256").update(body).digest("hex");
|
|
7661
|
+
const candidates = ["seq_orm_import", `seq_orm_${digest.slice(0, 16)}`, `seq_orm_${digest}`];
|
|
7662
|
+
const tag = candidates.find((candidate) => !body.includes(`$${candidate}$`)) ?? `seq_orm_${digest}_body`;
|
|
7663
|
+
return `$${tag}$${body}$${tag}$`;
|
|
7664
|
+
}
|
|
7665
|
+
|
|
7666
|
+
// src/import/script.ts
|
|
7667
|
+
function renderImportScript({
|
|
7668
|
+
namespace,
|
|
7669
|
+
schemaName,
|
|
7670
|
+
table,
|
|
7671
|
+
source,
|
|
7672
|
+
mapped,
|
|
7673
|
+
primaryKey: primaryKey2,
|
|
7674
|
+
onConflict,
|
|
7675
|
+
batchSize,
|
|
7676
|
+
omitted,
|
|
7677
|
+
resume
|
|
7678
|
+
}) {
|
|
7679
|
+
const sourceSql = `${quoteQualified(source.schema, source.table)} AS src`;
|
|
7680
|
+
const targetSql = quoteQualified(schemaName, table.table);
|
|
7681
|
+
const keys = primaryKey2.map((name2) => mapped.find((column) => column.column.columnName === name2)).filter((column) => column !== void 0);
|
|
7682
|
+
const probeSql = renderProbe({ sourceSql, keys, mapped });
|
|
7683
|
+
const procedure = renderProcedure({ targetSql, sourceSql, mapped, keys, onConflict, batchSize });
|
|
7684
|
+
const call = `CALL pg_temp.seq_orm_import(${resume.join(", ")});`;
|
|
7685
|
+
const omittedLine = omitted.length > 0 ? `-- Omitted columns keep their database default or NULL: ${omitted.join(", ")}` : "-- Every column is mapped.";
|
|
7686
|
+
const text4 = [
|
|
7687
|
+
"-- Sequence ORM data import",
|
|
7688
|
+
`-- namespace: ${namespace}`,
|
|
7689
|
+
`-- target: ${schemaName}.${table.table}`,
|
|
7690
|
+
`-- source: ${source.schema}.${source.table}`,
|
|
7691
|
+
`-- conflict: ${onConflict}`,
|
|
7692
|
+
`-- batch size: ${batchSize}`,
|
|
7693
|
+
"--",
|
|
7694
|
+
"-- Runs as the namespace owner. Does not evaluate insert or update policies,",
|
|
7695
|
+
"-- capability gates, or the operation journal. Sync the warehouse table into",
|
|
7696
|
+
"-- this Postgres relation first, review the probe, then run the load.",
|
|
7697
|
+
"-- skip and update are safe to re-run from the start after a failure. error stops on an existing key.",
|
|
7698
|
+
"-- Each batch commits. Do not resume from the maximum key in the target:",
|
|
7699
|
+
"-- a higher key that was already there would skip source rows this run never copied.",
|
|
7700
|
+
"-- --after is only the last key this procedure committed. This script does not print it.",
|
|
7701
|
+
omittedLine,
|
|
7702
|
+
"-- cast_* columns force every mapped value through the target type. An invalid value aborts before any insert.",
|
|
7703
|
+
"",
|
|
7704
|
+
probeSql,
|
|
7705
|
+
"",
|
|
7706
|
+
procedure,
|
|
7707
|
+
call,
|
|
7708
|
+
""
|
|
7709
|
+
].join("\n");
|
|
7710
|
+
return { probeSql, text: text4 };
|
|
7711
|
+
}
|
|
7712
|
+
function renderProbe({
|
|
7713
|
+
sourceSql,
|
|
7714
|
+
keys,
|
|
7715
|
+
mapped
|
|
7716
|
+
}) {
|
|
7717
|
+
const nullKey = keys.map((key) => `${key.expression} IS NULL`).join(" OR ");
|
|
7718
|
+
const distinct = keys.length === 1 ? keys[0].expression : `(${keys.map((key) => key.expression).join(", ")})`;
|
|
7719
|
+
const requiredNulls = nullChecked(mapped, keys).map((column) => `count(*) FILTER (WHERE src.${quoteIdentifier(column.source)} IS NULL) AS ${quoteIdentifier(`null_${column.column.columnName}`)}`);
|
|
7720
|
+
const selects = [
|
|
7721
|
+
"count(*) AS source_rows",
|
|
7722
|
+
`count(*) FILTER (WHERE ${nullKey}) AS null_primary_key`,
|
|
7723
|
+
`count(*) FILTER (WHERE NOT (${nullKey})) - count(DISTINCT ${distinct}) AS duplicate_primary_key`,
|
|
7724
|
+
...requiredNulls,
|
|
7725
|
+
...mapped.map((column) => `count(${column.expression}) AS ${quoteIdentifier(`cast_${column.column.columnName}`)}`)
|
|
7726
|
+
];
|
|
7727
|
+
return `SELECT
|
|
7728
|
+
${selects.join(",\n ")}
|
|
7729
|
+
FROM ${sourceSql};`;
|
|
7730
|
+
}
|
|
7731
|
+
function renderProcedure({
|
|
7732
|
+
targetSql,
|
|
7733
|
+
sourceSql,
|
|
7734
|
+
mapped,
|
|
7735
|
+
keys,
|
|
7736
|
+
onConflict,
|
|
7737
|
+
batchSize
|
|
7738
|
+
}) {
|
|
7739
|
+
const args = keys.map((key, index3) => `IN start_${index3} ${key.pgType} DEFAULT NULL`).join(", ");
|
|
7740
|
+
const declarations = [
|
|
7741
|
+
"DECLARE",
|
|
7742
|
+
" batch_scanned integer;",
|
|
7743
|
+
" batch_affected integer;",
|
|
7744
|
+
" total_rows bigint;",
|
|
7745
|
+
" null_keys bigint;",
|
|
7746
|
+
" distinct_keys bigint;",
|
|
7747
|
+
...keys.map((key, index3) => ` watermark_${index3} ${key.pgType};`),
|
|
7748
|
+
...requiredNullDeclarations(mapped, keys),
|
|
7749
|
+
...mapped.map((column) => ` cast_${column.column.columnName} bigint;`)
|
|
7750
|
+
];
|
|
7751
|
+
const body = [
|
|
7752
|
+
"BEGIN",
|
|
7753
|
+
...keys.map((_, index3) => ` watermark_${index3} := start_${index3};`),
|
|
7754
|
+
` ${renderGuard({ sourceSql, keys, mapped })}`,
|
|
7755
|
+
" LOOP",
|
|
7756
|
+
` ${renderPage({ targetSql, sourceSql, mapped, keys, onConflict, batchSize })}`,
|
|
7757
|
+
" EXIT WHEN batch_scanned = 0;",
|
|
7758
|
+
" RAISE NOTICE 'orm import scanned % affected %', batch_scanned, batch_affected;",
|
|
7759
|
+
" COMMIT;",
|
|
7760
|
+
" END LOOP;",
|
|
7761
|
+
"END"
|
|
7762
|
+
].join("\n");
|
|
7763
|
+
return [
|
|
7764
|
+
`CREATE OR REPLACE PROCEDURE pg_temp.seq_orm_import(${args})`,
|
|
7765
|
+
"LANGUAGE plpgsql",
|
|
7766
|
+
`AS ${dollarQuote(`
|
|
7767
|
+
${declarations.join("\n")}
|
|
7768
|
+
${body}
|
|
7769
|
+
`)}`,
|
|
7770
|
+
";",
|
|
7771
|
+
""
|
|
7772
|
+
].join("\n");
|
|
7773
|
+
}
|
|
7774
|
+
function nullChecked(mapped, keys) {
|
|
7775
|
+
return mapped.filter((column) => !column.fillsNull && column.column.nullable === false && !keys.includes(column));
|
|
7776
|
+
}
|
|
7777
|
+
function requiredNullDeclarations(mapped, keys) {
|
|
7778
|
+
return nullChecked(mapped, keys).map((column) => ` null_${column.column.columnName} bigint;`);
|
|
7779
|
+
}
|
|
7780
|
+
function renderGuard({
|
|
7781
|
+
sourceSql,
|
|
7782
|
+
keys,
|
|
7783
|
+
mapped
|
|
7784
|
+
}) {
|
|
7785
|
+
const nullKey = keys.map((key) => `${key.expression} IS NULL`).join(" OR ");
|
|
7786
|
+
const distinct = keys.length === 1 ? keys[0].expression : `(${keys.map((key) => key.expression).join(", ")})`;
|
|
7787
|
+
const nullColumns = nullChecked(mapped, keys);
|
|
7788
|
+
const into = [
|
|
7789
|
+
"total_rows",
|
|
7790
|
+
"null_keys",
|
|
7791
|
+
"distinct_keys",
|
|
7792
|
+
...nullColumns.map((column) => `null_${column.column.columnName}`),
|
|
7793
|
+
...mapped.map((column) => `cast_${column.column.columnName}`)
|
|
7794
|
+
];
|
|
7795
|
+
const selects = [
|
|
7796
|
+
"count(*)::bigint",
|
|
7797
|
+
`count(*) FILTER (WHERE ${nullKey})::bigint`,
|
|
7798
|
+
`count(DISTINCT ${distinct})::bigint`,
|
|
7799
|
+
...nullColumns.map((column) => `count(*) FILTER (WHERE src.${quoteIdentifier(column.source)} IS NULL)::bigint`),
|
|
7800
|
+
...mapped.map((column) => `count(${column.expression})::bigint`)
|
|
7801
|
+
];
|
|
7802
|
+
const checks = [
|
|
7803
|
+
"IF null_keys > 0 THEN RAISE EXCEPTION 'import refused: source primary key has % null values', null_keys; END IF;",
|
|
7804
|
+
"IF total_rows <> distinct_keys THEN RAISE EXCEPTION 'import refused: source primary key has % duplicate values', total_rows - distinct_keys; END IF;",
|
|
7805
|
+
...nullColumns.map(
|
|
7806
|
+
(column) => `IF null_${column.column.columnName} > 0 THEN RAISE EXCEPTION 'import refused: source column ${column.source} is null on % rows and ${column.column.columnName} is required', null_${column.column.columnName}; END IF;`
|
|
7807
|
+
)
|
|
7808
|
+
];
|
|
7809
|
+
return [`SELECT ${selects.join(", ")}`, ` INTO ${into.join(", ")}`, ` FROM ${sourceSql};`, ...checks.map((check) => ` ${check}`)].join("\n");
|
|
7810
|
+
}
|
|
7811
|
+
function renderPage({
|
|
7812
|
+
targetSql,
|
|
7813
|
+
sourceSql,
|
|
7814
|
+
mapped,
|
|
7815
|
+
keys,
|
|
7816
|
+
onConflict,
|
|
7817
|
+
batchSize
|
|
7818
|
+
}) {
|
|
7819
|
+
const keyExprs = keys.map((key) => key.expression);
|
|
7820
|
+
const where = `watermark_0 IS NULL OR (${keyExprs.join(", ")}) > (${keys.map((_, index3) => `watermark_${index3}`).join(", ")})`;
|
|
7821
|
+
const order = keyExprs.join(", ");
|
|
7822
|
+
const pageColumns = mapped.map((column) => `${column.expression} AS ${quoteIdentifier(column.column.columnName)}`);
|
|
7823
|
+
const insertColumns = mapped.map((column) => quoteIdentifier(column.column.columnName)).join(", ");
|
|
7824
|
+
const conflict = conflictClause(onConflict, keys, mapped);
|
|
7825
|
+
const keyOrder = keys.map((key) => `${quoteIdentifier(key.column.columnName)} DESC`).join(", ");
|
|
7826
|
+
const lastKeys = keys.map((key) => `(SELECT ${quoteIdentifier(key.column.columnName)} FROM page ORDER BY ${keyOrder} LIMIT 1)`).join(", ");
|
|
7827
|
+
const watermarkTargets = keys.map((_, index3) => `watermark_${index3}`).join(", ");
|
|
7828
|
+
return [
|
|
7829
|
+
"WITH page AS (",
|
|
7830
|
+
` SELECT ${pageColumns.join(", ")}`,
|
|
7831
|
+
` FROM ${sourceSql}`,
|
|
7832
|
+
` WHERE ${where}`,
|
|
7833
|
+
` ORDER BY ${order}`,
|
|
7834
|
+
` LIMIT ${batchSize}`,
|
|
7835
|
+
" ),",
|
|
7836
|
+
" ins AS (",
|
|
7837
|
+
` INSERT INTO ${targetSql} (${insertColumns})`,
|
|
7838
|
+
` SELECT ${insertColumns} FROM page`,
|
|
7839
|
+
` ${conflict}`,
|
|
7840
|
+
" RETURNING 1",
|
|
7841
|
+
" )",
|
|
7842
|
+
` SELECT (SELECT count(*)::int FROM page), (SELECT count(*)::int FROM ins), ${lastKeys}`,
|
|
7843
|
+
` INTO batch_scanned, batch_affected, ${watermarkTargets};`
|
|
7844
|
+
].join("\n");
|
|
7845
|
+
}
|
|
7846
|
+
function conflictClause(onConflict, keys, mapped) {
|
|
7847
|
+
if (onConflict === "error") return "";
|
|
7848
|
+
const target = keys.map((key) => quoteIdentifier(key.column.columnName)).join(", ");
|
|
7849
|
+
if (onConflict === "skip") return `ON CONFLICT (${target}) DO NOTHING`;
|
|
7850
|
+
const assignments = mapped.filter((column) => !keys.includes(column)).map((column) => `${quoteIdentifier(column.column.columnName)} = EXCLUDED.${quoteIdentifier(column.column.columnName)}`);
|
|
7851
|
+
return `ON CONFLICT (${target}) DO UPDATE SET ${assignments.join(", ")}`;
|
|
7852
|
+
}
|
|
7853
|
+
|
|
7854
|
+
// src/import/plan.ts
|
|
7855
|
+
function planTableImport({
|
|
7856
|
+
definition,
|
|
7857
|
+
spec,
|
|
7858
|
+
after
|
|
7859
|
+
}) {
|
|
7860
|
+
const parsedDefinition = canonicalDefinitionSchema.safeParse(definition);
|
|
7861
|
+
if (!parsedDefinition.success) return { ok: false, errors: ["namespace definition is not canonical ORM v2"] };
|
|
7862
|
+
const parsedSpec = importSpecSchema.safeParse(spec);
|
|
7863
|
+
if (!parsedSpec.success) {
|
|
7864
|
+
return { ok: false, errors: parsedSpec.error.issues.map((issue) => `${issue.path.join(".") || "spec"}: ${issue.message}`) };
|
|
7865
|
+
}
|
|
7866
|
+
return planParsed({ definition: parsedDefinition.data, spec: parsedSpec.data, after });
|
|
7867
|
+
}
|
|
7868
|
+
function planParsed({
|
|
7869
|
+
definition,
|
|
7870
|
+
spec,
|
|
7871
|
+
after
|
|
7872
|
+
}) {
|
|
7873
|
+
const source = splitQualifiedRelation(spec.from);
|
|
7874
|
+
const target = resolveTarget(definition, spec.table);
|
|
7875
|
+
const errors = [];
|
|
7876
|
+
if (!source) errors.push(`from '${spec.from}' is not a schema.table identifier`);
|
|
7877
|
+
else if (!sourceSchemaAllowed(source.schema)) errors.push(`from schema '${source.schema}' is not a warehouse relation`);
|
|
7878
|
+
if (!target.table) errors.push(...target.errors);
|
|
7879
|
+
if (!source || !target.table) return { ok: false, errors };
|
|
7880
|
+
const schemaName = ormSchemaName(definition.namespace);
|
|
7881
|
+
if (source.schema === schemaName && source.table === target.table.table) {
|
|
7882
|
+
errors.push(`from '${spec.from}' is the import target`);
|
|
7883
|
+
}
|
|
7884
|
+
const enums = new Map(definition.enums.map((entry) => [entry.name, entry]));
|
|
7885
|
+
const mapped = mapColumns({ table: target.table, spec, schemaName, enums, errors });
|
|
7886
|
+
const primaryKey2 = target.table.primaryKey ?? [];
|
|
7887
|
+
if (primaryKey2.length === 0) errors.push(`table '${target.table.table}' needs a primary key`);
|
|
7888
|
+
for (const key of primaryKey2) {
|
|
7889
|
+
if (!mapped.some((column) => column.column.columnName === key)) {
|
|
7890
|
+
errors.push(`primary key column '${key}' must be mapped so existing identities are preserved`);
|
|
7891
|
+
}
|
|
7892
|
+
}
|
|
7893
|
+
collectUnmapped({ table: target.table, mapped, errors });
|
|
7894
|
+
if (spec.onConflict === "update" && mapped.every((column) => primaryKey2.includes(column.column.columnName))) {
|
|
7895
|
+
errors.push('onConflict "update" needs a mapped column that is not part of the primary key');
|
|
7896
|
+
}
|
|
7897
|
+
const resume = resumeArguments({ mapped, primaryKey: primaryKey2, after, schemaName, enums, errors });
|
|
7898
|
+
if (errors.length > 0) return { ok: false, errors };
|
|
7899
|
+
const omitted = omittedColumns(target.table, mapped);
|
|
7900
|
+
const script = renderImportScript({
|
|
7901
|
+
namespace: definition.namespace,
|
|
7902
|
+
schemaName,
|
|
7903
|
+
table: target.table,
|
|
7904
|
+
source,
|
|
7905
|
+
mapped,
|
|
7906
|
+
primaryKey: primaryKey2,
|
|
7907
|
+
onConflict: spec.onConflict,
|
|
7908
|
+
batchSize: spec.batchSize,
|
|
7909
|
+
omitted,
|
|
7910
|
+
resume
|
|
7911
|
+
});
|
|
7912
|
+
const warnings = [
|
|
7913
|
+
"This load runs as the namespace owner. It does not evaluate insert or update policies.",
|
|
7914
|
+
...spec.onConflict === "error" ? ['onConflict "error" is not safe to re-run: the first existing key aborts the batch.'] : []
|
|
7915
|
+
];
|
|
7916
|
+
return {
|
|
7917
|
+
ok: true,
|
|
7918
|
+
plan: { probeSql: script.probeSql, script: script.text, batchSize: spec.batchSize, onConflict: spec.onConflict, warnings }
|
|
7919
|
+
};
|
|
7920
|
+
}
|
|
7921
|
+
function resolveTarget(definition, name2) {
|
|
7922
|
+
const table = definition.tables.find((candidate) => candidate.table === name2);
|
|
7923
|
+
if (!table) {
|
|
7924
|
+
const view = definition.views?.find((candidate) => candidate.view === name2);
|
|
7925
|
+
return { table: null, errors: [view ? `'${name2}' is a view, not an owned table` : `table '${name2}' is not in this namespace`] };
|
|
7926
|
+
}
|
|
7927
|
+
if (table.source !== void 0) return { table: null, errors: [`'${name2}' is a synced reflection and cannot be imported into`] };
|
|
7928
|
+
if (table.internal || table.materializes !== void 0) {
|
|
7929
|
+
return { table: null, errors: [`'${name2}' is an internal relation, not an import target`] };
|
|
7930
|
+
}
|
|
7931
|
+
return { table, errors: [] };
|
|
7932
|
+
}
|
|
7933
|
+
function mapColumns({
|
|
7934
|
+
table,
|
|
7935
|
+
spec,
|
|
7936
|
+
schemaName,
|
|
7937
|
+
enums,
|
|
7938
|
+
errors
|
|
7939
|
+
}) {
|
|
7940
|
+
const mapped = [];
|
|
7941
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7942
|
+
for (const entry of spec.columns) {
|
|
7943
|
+
const column = findColumn(table, entry.target);
|
|
7944
|
+
if (column === "ambiguous") {
|
|
7945
|
+
errors.push(`target '${entry.target}' matches more than one column`);
|
|
7946
|
+
continue;
|
|
7947
|
+
}
|
|
7948
|
+
if (!column) {
|
|
7949
|
+
errors.push(`target '${entry.target}' is not a column of '${table.table}'`);
|
|
7950
|
+
continue;
|
|
7951
|
+
}
|
|
7952
|
+
if (column.cell !== void 0) {
|
|
7953
|
+
errors.push(`target '${entry.target}' is cell metadata and cannot be imported`);
|
|
7954
|
+
continue;
|
|
7955
|
+
}
|
|
7956
|
+
if (seen.has(column.columnName)) {
|
|
7957
|
+
errors.push(`column '${column.columnName}' is mapped more than once`);
|
|
7958
|
+
continue;
|
|
7959
|
+
}
|
|
7960
|
+
if (!isSourceColumn(entry.source)) {
|
|
7961
|
+
errors.push(`source '${entry.source}' is not a column identifier`);
|
|
7962
|
+
continue;
|
|
7963
|
+
}
|
|
7964
|
+
seen.add(column.columnName);
|
|
7965
|
+
const pgType = columnPostgresType(column, schemaName, enums);
|
|
7966
|
+
if (!pgType) {
|
|
7967
|
+
errors.push(`column '${column.name}' references undeclared enum '${typeof column.type === "string" ? column.type : column.type.enum}'`);
|
|
7968
|
+
continue;
|
|
7969
|
+
}
|
|
7970
|
+
const expression = valueExpression({ column, source: entry.source, pgType, nullDefault: entry.nullDefault, schemaName, enums, errors });
|
|
7971
|
+
if (expression) mapped.push({ column, source: entry.source, expression, pgType, fillsNull: entry.nullDefault !== void 0 });
|
|
7972
|
+
}
|
|
7973
|
+
return mapped;
|
|
7974
|
+
}
|
|
7975
|
+
function findColumn(table, target) {
|
|
7976
|
+
const matches = table.columns.filter((column) => column.name === target || column.columnName === target);
|
|
7977
|
+
if (matches.length > 1) return "ambiguous";
|
|
7978
|
+
return matches[0];
|
|
7979
|
+
}
|
|
7980
|
+
function valueExpression({
|
|
7981
|
+
column,
|
|
7982
|
+
source,
|
|
7983
|
+
pgType,
|
|
7984
|
+
nullDefault,
|
|
7985
|
+
schemaName,
|
|
7986
|
+
enums,
|
|
7987
|
+
errors
|
|
7988
|
+
}) {
|
|
7989
|
+
const cast = `CAST(src.${quoteIdentifier(source)} AS ${pgType})`;
|
|
7990
|
+
if (nullDefault === void 0) return cast;
|
|
7991
|
+
const literal2 = literalSql({ column, value: nullDefault, schemaName, enums });
|
|
7992
|
+
if (!literal2.ok) {
|
|
7993
|
+
errors.push(literal2.error);
|
|
7994
|
+
return null;
|
|
7995
|
+
}
|
|
7996
|
+
return `COALESCE(${cast}, ${literal2.sql})`;
|
|
7997
|
+
}
|
|
7998
|
+
function collectUnmapped({
|
|
7999
|
+
table,
|
|
8000
|
+
mapped,
|
|
8001
|
+
errors
|
|
8002
|
+
}) {
|
|
8003
|
+
const mappedNames = new Set(mapped.map((column) => column.column.columnName));
|
|
8004
|
+
const primaryKey2 = new Set(table.primaryKey ?? []);
|
|
8005
|
+
for (const column of table.columns) {
|
|
8006
|
+
if (mappedNames.has(column.columnName) || column.cell !== void 0) continue;
|
|
8007
|
+
const hasDefault = column.default !== void 0 && column.default !== null || column.defaultFn !== void 0;
|
|
8008
|
+
const required = column.nullable === false && !hasDefault && !primaryKey2.has(column.columnName);
|
|
8009
|
+
if (required) {
|
|
8010
|
+
errors.push(`column '${column.name}' is required and has no default \u2014 map it or set a database default before importing`);
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
8013
|
+
}
|
|
8014
|
+
function omittedColumns(table, mapped) {
|
|
8015
|
+
const mappedNames = new Set(mapped.map((column) => column.column.columnName));
|
|
8016
|
+
return table.columns.filter((column) => column.cell === void 0 && !mappedNames.has(column.columnName)).map((column) => column.columnName);
|
|
8017
|
+
}
|
|
8018
|
+
function resumeArguments({
|
|
8019
|
+
mapped,
|
|
8020
|
+
primaryKey: primaryKey2,
|
|
8021
|
+
after,
|
|
8022
|
+
schemaName,
|
|
8023
|
+
enums,
|
|
8024
|
+
errors
|
|
8025
|
+
}) {
|
|
8026
|
+
if (after === void 0) return primaryKey2.map(() => "NULL");
|
|
8027
|
+
if (primaryKey2.length !== 1) {
|
|
8028
|
+
errors.push("after resumes a single-column primary key; re-run from the start for a composite key (skip and update are idempotent)");
|
|
8029
|
+
return [];
|
|
8030
|
+
}
|
|
8031
|
+
const key = mapped.find((column) => column.column.columnName === primaryKey2[0]);
|
|
8032
|
+
if (!key) return [];
|
|
8033
|
+
const literal2 = resumeLiteralSql({ column: key.column, after, schemaName, enums });
|
|
8034
|
+
if (!literal2.ok) {
|
|
8035
|
+
errors.push(`after: ${literal2.error}`);
|
|
8036
|
+
return [];
|
|
8037
|
+
}
|
|
8038
|
+
return [literal2.sql];
|
|
8039
|
+
}
|
|
7279
8040
|
|
|
7280
8041
|
Object.defineProperty(exports, "capabilityPinsSchema", {
|
|
7281
8042
|
enumerable: true,
|
|
@@ -7315,14 +8076,17 @@ exports.COLUMN_DEFAULT_FNS = COLUMN_DEFAULT_FNS;
|
|
|
7315
8076
|
exports.COLUMN_IDENTIFIER_PATTERN = COLUMN_IDENTIFIER_PATTERN;
|
|
7316
8077
|
exports.COLUMN_TYPES = COLUMN_TYPES2;
|
|
7317
8078
|
exports.COMPARE_OPS = COMPARE_OPS;
|
|
8079
|
+
exports.COMPOSED_IR_VERSION = COMPOSED_IR_VERSION;
|
|
7318
8080
|
exports.CONSUMER_ROLE_SUFFIX = CONSUMER_ROLE_SUFFIX;
|
|
7319
8081
|
exports.ColumnBuilder = ColumnBuilder;
|
|
7320
8082
|
exports.DATE_BUCKETS = DATE_BUCKETS;
|
|
7321
8083
|
exports.DEFINITION_FILE_EXTENSIONS = DEFINITION_FILE_EXTENSIONS;
|
|
7322
8084
|
exports.ENUM_BRAND = ENUM_BRAND;
|
|
8085
|
+
exports.GUARDED_IR_VERSION = GUARDED_IR_VERSION;
|
|
7323
8086
|
exports.HASH_DOMAIN = HASH_DOMAIN;
|
|
7324
8087
|
exports.IDENTIFIER_PATTERN = IDENTIFIER_PATTERN;
|
|
7325
8088
|
exports.IDENTIFIER_RE = IDENTIFIER_RE;
|
|
8089
|
+
exports.IMPORT_CONFLICT_MODES = IMPORT_CONFLICT_MODES;
|
|
7326
8090
|
exports.INPUT_REF_KINDS = INPUT_REF_KINDS;
|
|
7327
8091
|
exports.IR_VERSION = IR_VERSION;
|
|
7328
8092
|
exports.IrVersionError = IrVersionError;
|
|
@@ -7352,7 +8116,9 @@ exports.QUALIFIED_TABLE_PATTERN = QUALIFIED_TABLE_PATTERN;
|
|
|
7352
8116
|
exports.RESERVED_COLUMN_NAMES = RESERVED_COLUMN_NAMES;
|
|
7353
8117
|
exports.RESERVED_TABLE_NAMES = RESERVED_TABLE_NAMES;
|
|
7354
8118
|
exports.ROLES_CLAIM_KEY = ROLES_CLAIM_KEY;
|
|
8119
|
+
exports.RUNTIME_SQL_IR_VERSION = RUNTIME_SQL_IR_VERSION;
|
|
7355
8120
|
exports.SCHEMA_DIR = SCHEMA_DIR;
|
|
8121
|
+
exports.SQL_IR_VERSION = SQL_IR_VERSION;
|
|
7356
8122
|
exports.STATEMENT_KINDS = STATEMENT_KINDS;
|
|
7357
8123
|
exports.SYNCED_CONTRACT_HASH_DOMAIN = SYNCED_CONTRACT_HASH_DOMAIN;
|
|
7358
8124
|
exports.SYNCED_SOURCE_PATTERN = SYNCED_SOURCE_PATTERN;
|
|
@@ -7413,6 +8179,7 @@ exports.custom = custom;
|
|
|
7413
8179
|
exports.declaredCapabilityKeys = declaredCapabilityKeys;
|
|
7414
8180
|
exports.defineEnum = defineEnum;
|
|
7415
8181
|
exports.defineNamespace = defineNamespace;
|
|
8182
|
+
exports.defineOperation = defineOperation;
|
|
7416
8183
|
exports.defineResolvedTable = defineResolvedTable;
|
|
7417
8184
|
exports.defineSyncedTable = defineSyncedTable;
|
|
7418
8185
|
exports.defineTable = defineTable;
|
|
@@ -7431,6 +8198,7 @@ exports.generateSnapshot = generateSnapshot;
|
|
|
7431
8198
|
exports.generateSnapshotV2 = generateSnapshotV2;
|
|
7432
8199
|
exports.highest = highest;
|
|
7433
8200
|
exports.identifierSchema = identifierSchema;
|
|
8201
|
+
exports.importSpecSchema = importSpecSchema;
|
|
7434
8202
|
exports.indexByHash = indexByHash;
|
|
7435
8203
|
exports.isColumnBuilder = isColumnBuilder;
|
|
7436
8204
|
exports.isEnumRef = isEnumRef;
|
|
@@ -7460,6 +8228,8 @@ exports.nonNullFirst = nonNullFirst;
|
|
|
7460
8228
|
exports.normalizeCanonical = normalizeCanonical;
|
|
7461
8229
|
exports.normalizeIr = normalizeIr;
|
|
7462
8230
|
exports.normalizeSnapshot = normalizeSnapshot;
|
|
8231
|
+
exports.objectRef = objectRef;
|
|
8232
|
+
exports.ontologyContractSchema = ontologyContractSchema;
|
|
7463
8233
|
exports.orderIrSchema = orderIrSchema;
|
|
7464
8234
|
exports.ormMigrationExportSchema = ormMigrationExportSchema;
|
|
7465
8235
|
exports.ormSchemaName = ormSchemaName;
|
|
@@ -7476,6 +8246,7 @@ exports.pendingKitStatementsV2 = pendingKitStatementsV2;
|
|
|
7476
8246
|
exports.planCommittedMigrationV2 = planCommittedMigrationV2;
|
|
7477
8247
|
exports.planMigration = planMigration;
|
|
7478
8248
|
exports.planMigrationV2 = planMigrationV2;
|
|
8249
|
+
exports.planTableImport = planTableImport;
|
|
7479
8250
|
exports.policyExpressionSchema = policyExpressionSchema;
|
|
7480
8251
|
exports.policyVariantSchema = policyVariantSchema;
|
|
7481
8252
|
exports.predIrSchema = predIrSchema;
|
|
@@ -7487,6 +8258,7 @@ exports.quoteIdentifier = quoteIdentifier;
|
|
|
7487
8258
|
exports.quoteLiteral = quoteLiteral;
|
|
7488
8259
|
exports.readMigrationSql = readMigrationSql;
|
|
7489
8260
|
exports.readMigrationWorkspace = readMigrationWorkspace;
|
|
8261
|
+
exports.readOntologyContract = readOntologyContract;
|
|
7490
8262
|
exports.readSnapshot = readSnapshot;
|
|
7491
8263
|
exports.rebaseViolations = rebaseViolations;
|
|
7492
8264
|
exports.referencedCapabilities = referencedCapabilities;
|
|
@@ -7518,6 +8290,7 @@ exports.toMany = toMany;
|
|
|
7518
8290
|
exports.toOne = toOne;
|
|
7519
8291
|
exports.toPlatformUser = toPlatformUser;
|
|
7520
8292
|
exports.typesInterfaceName = typesInterfaceName;
|
|
8293
|
+
exports.validateNamespaceCatalog = validateNamespaceCatalog;
|
|
7521
8294
|
exports.validateViewCatalogStatement = validateViewCatalogStatement;
|
|
7522
8295
|
exports.valueIrSchema = valueIrSchema;
|
|
7523
8296
|
exports.variableDefaultIrSchema = variableDefaultIrSchema;
|