@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/authoring.js
CHANGED
|
@@ -155,7 +155,8 @@ var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
|
|
|
155
155
|
var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
156
156
|
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
157
157
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
158
|
-
var
|
|
158
|
+
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
|
|
159
|
+
var SYNCED_SOURCE_PATTERN = QUALIFIED_TABLE_PATTERN;
|
|
159
160
|
var HASH_DOMAIN = "sequence-orm-definitions\0";
|
|
160
161
|
var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
|
|
161
162
|
var CELL_PART_SUFFIX = {
|
|
@@ -232,6 +233,9 @@ function registerNamespaceConfig(value) {
|
|
|
232
233
|
// src/authoring/table.ts
|
|
233
234
|
var TABLE_BRAND = "seq-table";
|
|
234
235
|
var LINK_BRAND = "seq-link";
|
|
236
|
+
function objectRef(namespace, table) {
|
|
237
|
+
return { kind: "seq-external-object", namespace, table };
|
|
238
|
+
}
|
|
235
239
|
function toOne(target, options) {
|
|
236
240
|
return {
|
|
237
241
|
kind: LINK_BRAND,
|
|
@@ -285,51 +289,51 @@ function assertIdentifier({ value, what, file, pattern = IDENTIFIER_PATTERN }) {
|
|
|
285
289
|
);
|
|
286
290
|
}
|
|
287
291
|
}
|
|
288
|
-
function defineTable(
|
|
292
|
+
function defineTable(name2, columns, config = {}) {
|
|
289
293
|
const file = activeAuthoringContext()?.currentFile;
|
|
290
|
-
assertIdentifier({ value:
|
|
294
|
+
assertIdentifier({ value: name2, what: "table name", file });
|
|
291
295
|
const entries = Object.entries(columns);
|
|
292
296
|
if (entries.length === 0) {
|
|
293
|
-
throw new OrmDefinitionError(`table "${
|
|
297
|
+
throw new OrmDefinitionError(`table "${name2}" declares no columns`, file);
|
|
294
298
|
}
|
|
295
299
|
const seenColumnNames = /* @__PURE__ */ new Map();
|
|
296
300
|
for (const [propertyName, builder] of entries) {
|
|
297
301
|
if (!isColumnBuilder(builder)) {
|
|
298
302
|
throw new OrmDefinitionError(
|
|
299
|
-
`table "${
|
|
303
|
+
`table "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
|
|
300
304
|
file
|
|
301
305
|
);
|
|
302
306
|
}
|
|
303
307
|
const columnName = builder.columnNameFor(propertyName);
|
|
304
|
-
assertIdentifier({ value: columnName, what: `table "${
|
|
308
|
+
assertIdentifier({ value: columnName, what: `table "${name2}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
|
|
305
309
|
if (config.materializes === void 0 && (propertyName === CELLS_FIELD || Object.values(CELL_PART_SUFFIX).some((suffix) => columnName.endsWith(suffix) || propertyName.endsWith(suffix)))) {
|
|
306
310
|
throw new OrmDefinitionError(
|
|
307
|
-
`table "${
|
|
311
|
+
`table "${name2}": "${propertyName}" collides with the reserved cell-metadata names ('${CELLS_FIELD}', or a ${Object.values(CELL_PART_SUFFIX).join("/")} suffix)`,
|
|
308
312
|
file
|
|
309
313
|
);
|
|
310
314
|
}
|
|
311
315
|
const existing = seenColumnNames.get(columnName);
|
|
312
316
|
if (existing !== void 0) {
|
|
313
317
|
throw new OrmDefinitionError(
|
|
314
|
-
`table "${
|
|
318
|
+
`table "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
|
|
315
319
|
file
|
|
316
320
|
);
|
|
317
321
|
}
|
|
318
322
|
seenColumnNames.set(columnName, propertyName);
|
|
319
323
|
}
|
|
320
|
-
const table = { kind: TABLE_BRAND, tableName:
|
|
324
|
+
const table = { kind: TABLE_BRAND, tableName: name2, columns, config, source: void 0, file };
|
|
321
325
|
registerTable(table);
|
|
322
326
|
return table;
|
|
323
327
|
}
|
|
324
|
-
function defineSyncedTable(
|
|
328
|
+
function defineSyncedTable(name2, columnsOrConfig, maybeConfig) {
|
|
325
329
|
const file = activeAuthoringContext()?.currentFile;
|
|
326
|
-
assertIdentifier({ value:
|
|
330
|
+
assertIdentifier({ value: name2, what: "table name", file });
|
|
327
331
|
const isTyped = maybeConfig !== void 0;
|
|
328
332
|
const columns = isTyped ? columnsOrConfig : {};
|
|
329
333
|
const config = isTyped ? maybeConfig : columnsOrConfig;
|
|
330
334
|
if (!SYNCED_SOURCE_PATTERN.test(config.source)) {
|
|
331
335
|
throw new OrmDefinitionError(
|
|
332
|
-
`synced table "${
|
|
336
|
+
`synced table "${name2}": source "${config.source}" must be a qualified lowercase schema.table identifier`,
|
|
333
337
|
file
|
|
334
338
|
);
|
|
335
339
|
}
|
|
@@ -339,21 +343,21 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
|
|
|
339
343
|
for (const [propertyName, builder] of entries) {
|
|
340
344
|
if (!isColumnBuilder(builder)) {
|
|
341
345
|
throw new OrmDefinitionError(
|
|
342
|
-
`synced table "${
|
|
346
|
+
`synced table "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
|
|
343
347
|
file
|
|
344
348
|
);
|
|
345
349
|
}
|
|
346
350
|
const columnName = builder.columnNameFor(propertyName);
|
|
347
351
|
assertIdentifier({
|
|
348
352
|
value: columnName,
|
|
349
|
-
what: `synced table "${
|
|
353
|
+
what: `synced table "${name2}": column "${propertyName}" column name`,
|
|
350
354
|
file,
|
|
351
355
|
pattern: COLUMN_IDENTIFIER_PATTERN
|
|
352
356
|
});
|
|
353
357
|
const existing = seenColumnNames.get(columnName);
|
|
354
358
|
if (existing !== void 0) {
|
|
355
359
|
throw new OrmDefinitionError(
|
|
356
|
-
`synced table "${
|
|
360
|
+
`synced table "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
|
|
357
361
|
file
|
|
358
362
|
);
|
|
359
363
|
}
|
|
@@ -364,13 +368,13 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
|
|
|
364
368
|
const hasConfigPk = config.primaryKey !== void 0 && config.primaryKey.length > 0;
|
|
365
369
|
if (columnLevelPk.length === 0 && !hasConfigPk) {
|
|
366
370
|
throw new OrmDefinitionError(
|
|
367
|
-
`synced table "${
|
|
371
|
+
`synced table "${name2}" declares columns but no primary key \u2014 use .primaryKey() or config.primaryKey`,
|
|
368
372
|
file
|
|
369
373
|
);
|
|
370
374
|
}
|
|
371
375
|
if (columnLevelPk.length > 0 && hasConfigPk) {
|
|
372
376
|
throw new OrmDefinitionError(
|
|
373
|
-
`synced table "${
|
|
377
|
+
`synced table "${name2}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
|
|
374
378
|
file
|
|
375
379
|
);
|
|
376
380
|
}
|
|
@@ -382,7 +386,7 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
|
|
|
382
386
|
if (config.policies !== void 0) tableConfig.policies = { read: config.policies.read };
|
|
383
387
|
const table = {
|
|
384
388
|
kind: TABLE_BRAND,
|
|
385
|
-
tableName:
|
|
389
|
+
tableName: name2,
|
|
386
390
|
columns,
|
|
387
391
|
config: tableConfig,
|
|
388
392
|
source: config.source,
|
|
@@ -391,26 +395,26 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
|
|
|
391
395
|
registerTable(table);
|
|
392
396
|
return table;
|
|
393
397
|
}
|
|
394
|
-
function defineEnum(
|
|
398
|
+
function defineEnum(name2, values) {
|
|
395
399
|
const file = activeAuthoringContext()?.currentFile;
|
|
396
|
-
assertIdentifier({ value:
|
|
400
|
+
assertIdentifier({ value: name2, what: "enum name", file });
|
|
397
401
|
if (values.length === 0 || values.length > 100) {
|
|
398
|
-
throw new OrmDefinitionError(`enum "${
|
|
402
|
+
throw new OrmDefinitionError(`enum "${name2}" must declare between 1 and 100 values`, file);
|
|
399
403
|
}
|
|
400
404
|
const seen = /* @__PURE__ */ new Set();
|
|
401
405
|
for (const value of values) {
|
|
402
406
|
if (value.length === 0 || value.length > MAX_IDENTIFIER_LENGTH) {
|
|
403
407
|
throw new OrmDefinitionError(
|
|
404
|
-
`enum "${
|
|
408
|
+
`enum "${name2}": value ${JSON.stringify(value)} must be 1-${MAX_IDENTIFIER_LENGTH} characters`,
|
|
405
409
|
file
|
|
406
410
|
);
|
|
407
411
|
}
|
|
408
412
|
if (seen.has(value)) {
|
|
409
|
-
throw new OrmDefinitionError(`enum "${
|
|
413
|
+
throw new OrmDefinitionError(`enum "${name2}" declares duplicate value "${value}"`, file);
|
|
410
414
|
}
|
|
411
415
|
seen.add(value);
|
|
412
416
|
}
|
|
413
|
-
const ref = { kind: ENUM_BRAND, name, values: [...values] };
|
|
417
|
+
const ref = { kind: ENUM_BRAND, name: name2, values: [...values] };
|
|
414
418
|
registerEnum(ref);
|
|
415
419
|
return ref;
|
|
416
420
|
}
|
|
@@ -418,52 +422,52 @@ var VIEW_BRAND = "seq-view";
|
|
|
418
422
|
function isSeqView(value) {
|
|
419
423
|
return typeof value === "object" && value !== null && value.kind === VIEW_BRAND;
|
|
420
424
|
}
|
|
421
|
-
function defineView(
|
|
425
|
+
function defineView(name2, columns, config) {
|
|
422
426
|
const file = activeAuthoringContext()?.currentFile;
|
|
423
|
-
if (!IDENTIFIER_PATTERN.test(
|
|
427
|
+
if (!IDENTIFIER_PATTERN.test(name2) || name2.length > MAX_IDENTIFIER_LENGTH) {
|
|
424
428
|
throw new OrmDefinitionError(
|
|
425
|
-
`view name "${
|
|
429
|
+
`view name "${name2}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
|
|
426
430
|
file
|
|
427
431
|
);
|
|
428
432
|
}
|
|
429
433
|
const entries = Object.entries(columns);
|
|
430
|
-
if (entries.length === 0) throw new OrmDefinitionError(`view "${
|
|
434
|
+
if (entries.length === 0) throw new OrmDefinitionError(`view "${name2}" declares no columns`, file);
|
|
431
435
|
const seenColumnNames = /* @__PURE__ */ new Map();
|
|
432
436
|
for (const [propertyName, builder] of entries) {
|
|
433
437
|
if (!isColumnBuilder(builder)) {
|
|
434
438
|
throw new OrmDefinitionError(
|
|
435
|
-
`view "${
|
|
439
|
+
`view "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories`,
|
|
436
440
|
file
|
|
437
441
|
);
|
|
438
442
|
}
|
|
439
443
|
const columnName = builder.columnNameFor(propertyName);
|
|
440
444
|
if (!COLUMN_IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
|
|
441
|
-
throw new OrmDefinitionError(`view "${
|
|
445
|
+
throw new OrmDefinitionError(`view "${name2}": column "${propertyName}" has invalid column name "${columnName}"`, file);
|
|
442
446
|
}
|
|
443
447
|
const existing = seenColumnNames.get(columnName);
|
|
444
448
|
if (existing !== void 0) {
|
|
445
449
|
throw new OrmDefinitionError(
|
|
446
|
-
`view "${
|
|
450
|
+
`view "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
|
|
447
451
|
file
|
|
448
452
|
);
|
|
449
453
|
}
|
|
450
454
|
seenColumnNames.set(columnName, propertyName);
|
|
451
455
|
}
|
|
452
|
-
const view = { kind: VIEW_BRAND, viewName:
|
|
456
|
+
const view = { kind: VIEW_BRAND, viewName: name2, columns, config, file };
|
|
453
457
|
registerView(view);
|
|
454
458
|
return view;
|
|
455
459
|
}
|
|
456
460
|
var QUERY_COLUMN_FACTORIES = {
|
|
457
|
-
uuid: (
|
|
458
|
-
text: (
|
|
459
|
-
integer: (
|
|
460
|
-
bigint: (
|
|
461
|
-
numeric: (
|
|
462
|
-
double: (
|
|
463
|
-
boolean: (
|
|
464
|
-
timestamptz: (
|
|
465
|
-
date: (
|
|
466
|
-
jsonb: (
|
|
461
|
+
uuid: (name2) => uuid(name2),
|
|
462
|
+
text: (name2) => text(name2),
|
|
463
|
+
integer: (name2) => integer(name2),
|
|
464
|
+
bigint: (name2) => bigint(name2, { mode: "bigint" }),
|
|
465
|
+
numeric: (name2) => numeric(name2),
|
|
466
|
+
double: (name2) => doublePrecision(name2),
|
|
467
|
+
boolean: (name2) => boolean(name2),
|
|
468
|
+
timestamptz: (name2) => timestamp(name2, { withTimezone: true }),
|
|
469
|
+
date: (name2) => date(name2),
|
|
470
|
+
jsonb: (name2) => jsonb(name2)
|
|
467
471
|
};
|
|
468
472
|
function compileViewQueries(input) {
|
|
469
473
|
const schemaName = `orm_${input.namespace}`;
|
|
@@ -519,9 +523,9 @@ function compileViewQueries(input) {
|
|
|
519
523
|
}
|
|
520
524
|
const handle = handles.get(resolved);
|
|
521
525
|
if (!handle) {
|
|
522
|
-
const
|
|
526
|
+
const name2 = isSeqTable(resolved) ? resolved.tableName : resolved.viewName;
|
|
523
527
|
throw new OrmDefinitionError(
|
|
524
|
-
`view "${authored.viewName}" references relation "${
|
|
528
|
+
`view "${authored.viewName}" references relation "${name2}" which is not part of this namespace`,
|
|
525
529
|
authored.file
|
|
526
530
|
);
|
|
527
531
|
}
|
|
@@ -712,8 +716,8 @@ function overlayWinsUnion(input) {
|
|
|
712
716
|
// src/authoring/cow-mutations.ts
|
|
713
717
|
function overrideRowMutation(input) {
|
|
714
718
|
const { overlay, synced, pk } = input;
|
|
715
|
-
const
|
|
716
|
-
assertMutationName(
|
|
719
|
+
const name2 = input.name ?? `override_${overlay.tableName}`;
|
|
720
|
+
assertMutationName(name2, "overrideRowMutation");
|
|
717
721
|
assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "overrideRowMutation" });
|
|
718
722
|
const sharedProperties = sharedNonPkProperties({ overlay, synced, pk });
|
|
719
723
|
const setProperties = resolveSetProperties({
|
|
@@ -732,7 +736,7 @@ function overrideRowMutation(input) {
|
|
|
732
736
|
});
|
|
733
737
|
return {
|
|
734
738
|
kind: "override",
|
|
735
|
-
name,
|
|
739
|
+
name: name2,
|
|
736
740
|
synced: synced.tableName,
|
|
737
741
|
pk,
|
|
738
742
|
copy: [pk, ...sharedProperties],
|
|
@@ -741,23 +745,23 @@ function overrideRowMutation(input) {
|
|
|
741
745
|
}
|
|
742
746
|
function revertOverlayMutation(input) {
|
|
743
747
|
const { overlay, synced, pk } = input;
|
|
744
|
-
const
|
|
745
|
-
assertMutationName(
|
|
748
|
+
const name2 = input.name ?? `revert_${overlay.tableName}`;
|
|
749
|
+
assertMutationName(name2, "revertOverlayMutation");
|
|
746
750
|
assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "revertOverlayMutation" });
|
|
747
751
|
assertMatchingColumns({ overlay, synced, properties: [pk], helper: "revertOverlayMutation" });
|
|
748
752
|
return {
|
|
749
753
|
kind: "revert",
|
|
750
|
-
name,
|
|
754
|
+
name: name2,
|
|
751
755
|
synced: synced.tableName,
|
|
752
756
|
pk,
|
|
753
757
|
copy: [],
|
|
754
758
|
set: []
|
|
755
759
|
};
|
|
756
760
|
}
|
|
757
|
-
function assertMutationName(
|
|
758
|
-
if (!IDENTIFIER_PATTERN.test(
|
|
761
|
+
function assertMutationName(name2, helper) {
|
|
762
|
+
if (!IDENTIFIER_PATTERN.test(name2) || name2.length > MAX_IDENTIFIER_LENGTH) {
|
|
759
763
|
throw new OrmDefinitionError(
|
|
760
|
-
`${helper}: mutation name "${
|
|
764
|
+
`${helper}: mutation name "${name2}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`
|
|
761
765
|
);
|
|
762
766
|
}
|
|
763
767
|
}
|
|
@@ -883,7 +887,7 @@ function scalarColumnType(table, property, helper) {
|
|
|
883
887
|
}
|
|
884
888
|
return type;
|
|
885
889
|
}
|
|
886
|
-
var quoteIdentifier = (
|
|
890
|
+
var quoteIdentifier = (name2) => `"${name2.replaceAll('"', '""')}"`;
|
|
887
891
|
var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
888
892
|
|
|
889
893
|
// src/checks/compile.ts
|
|
@@ -1217,8 +1221,8 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
1217
1221
|
validateRules(scope);
|
|
1218
1222
|
validateViewCells(scope);
|
|
1219
1223
|
if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
|
|
1220
|
-
for (const
|
|
1221
|
-
if (scope.sourceByName.get(
|
|
1224
|
+
for (const name2 of resolved.deletedBy) {
|
|
1225
|
+
if (scope.sourceByName.get(name2)?.kind !== "manual") fail(`deletedBy '${name2}' must be the manual source`);
|
|
1222
1226
|
}
|
|
1223
1227
|
return errors;
|
|
1224
1228
|
}
|
|
@@ -1315,7 +1319,7 @@ function validateViewCells({ view, dataColumns, pkColumn, fail }) {
|
|
|
1315
1319
|
if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
|
|
1316
1320
|
expected.delete(column.name);
|
|
1317
1321
|
}
|
|
1318
|
-
for (const
|
|
1322
|
+
for (const name2 of expected.keys()) fail(`missing cell column '${name2}'`);
|
|
1319
1323
|
}
|
|
1320
1324
|
var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
|
|
1321
1325
|
function validateMaterialized({ scope, tableByName, views }) {
|
|
@@ -1406,6 +1410,8 @@ var canonicalLinkSchema = z.object({
|
|
|
1406
1410
|
kind: z.enum(["toOne", "toMany", "toPlatformUser"]),
|
|
1407
1411
|
/** Target table name (namespace-local). Absent for toPlatformUser. */
|
|
1408
1412
|
target: identifier2.optional(),
|
|
1413
|
+
/** Owning namespace of an imported target; omitted for local links. */
|
|
1414
|
+
namespace: identifier2.max(41).optional(),
|
|
1409
1415
|
/** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
|
|
1410
1416
|
on: columnIdentifier,
|
|
1411
1417
|
/** Referenced column on the target (default: target's PK / platform user `sub`). */
|
|
@@ -1664,11 +1670,12 @@ var canonicalForeignKeySchema = z.object({
|
|
|
1664
1670
|
}).strict();
|
|
1665
1671
|
var canonicalTableSchema = z.object({
|
|
1666
1672
|
table: identifier2,
|
|
1673
|
+
displayName: z.string().trim().min(1).max(120).optional(),
|
|
1667
1674
|
description: z.string().max(2e3).optional(),
|
|
1668
1675
|
/** Table-level rename hint (new in v2). */
|
|
1669
1676
|
renamedFrom: identifier2.optional(),
|
|
1670
|
-
/** Synced/reflected read-only tables (`source:
|
|
1671
|
-
source: z.string().regex(
|
|
1677
|
+
/** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
|
|
1678
|
+
source: z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
|
|
1672
1679
|
columns: z.array(canonicalColumnSchema).default([]),
|
|
1673
1680
|
primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
|
|
1674
1681
|
links: z.array(canonicalLinkSchema).default([]),
|
|
@@ -1821,7 +1828,9 @@ function validateCellMetadata(table, index, ctx) {
|
|
|
1821
1828
|
}
|
|
1822
1829
|
}
|
|
1823
1830
|
}
|
|
1824
|
-
var
|
|
1831
|
+
var exportGateSchema = z.union([z.literal(true), z.object({ capability: z.string().min(1) }).strict(), z.object({ role: z.string().min(1) }).strict()]);
|
|
1832
|
+
var relationExportSchema = z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
|
|
1833
|
+
var localDefinitionSchema = z.object({
|
|
1825
1834
|
definition_format: z.literal(CANONICAL_FORMAT),
|
|
1826
1835
|
namespace: identifier2.max(41),
|
|
1827
1836
|
description: z.string().max(2e3).optional(),
|
|
@@ -1829,6 +1838,8 @@ var canonicalDefinitionSchema = z.object({
|
|
|
1829
1838
|
tables: z.array(canonicalTableSchema).min(0).max(500),
|
|
1830
1839
|
/** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
|
|
1831
1840
|
views: z.array(canonicalViewSchema).max(500).optional(),
|
|
1841
|
+
/** Target-owned cross-namespace access contract, using existing namespace grants. */
|
|
1842
|
+
exports: z.record(identifier2, relationExportSchema).optional(),
|
|
1832
1843
|
/** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
|
|
1833
1844
|
capabilities: z.object({
|
|
1834
1845
|
roles: z.unknown().optional(),
|
|
@@ -1855,7 +1866,16 @@ var canonicalDefinitionSchema = z.object({
|
|
|
1855
1866
|
}
|
|
1856
1867
|
}
|
|
1857
1868
|
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1869
|
+
const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
|
|
1870
|
+
for (const name2 of Object.keys(definition.exports ?? {})) {
|
|
1871
|
+
if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
|
|
1872
|
+
}
|
|
1858
1873
|
for (const [tableIndex, table] of definition.tables.entries()) {
|
|
1874
|
+
for (const [linkIndex, link] of table.links.entries()) {
|
|
1875
|
+
if (link.enforce && link.namespace !== void 0 && link.namespace !== definition.namespace) {
|
|
1876
|
+
ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", linkIndex], message: "physical foreign keys require a local target; use an ORM link without enforce" });
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1859
1879
|
const names = /* @__PURE__ */ new Set([
|
|
1860
1880
|
...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
|
|
1861
1881
|
...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
|
|
@@ -1892,14 +1912,14 @@ var canonicalDefinitionSchema = z.object({
|
|
|
1892
1912
|
}
|
|
1893
1913
|
if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
|
|
1894
1914
|
if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
|
|
1895
|
-
for (const [position,
|
|
1896
|
-
const sourceColumn = table.columns.find((column) => column.columnName ===
|
|
1915
|
+
for (const [position, name2] of fk.columns.entries()) {
|
|
1916
|
+
const sourceColumn = table.columns.find((column) => column.columnName === name2);
|
|
1897
1917
|
const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
|
|
1898
1918
|
if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
|
|
1899
1919
|
else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
|
|
1900
1920
|
}
|
|
1901
1921
|
if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
|
|
1902
|
-
if (fk.columns.some((
|
|
1922
|
+
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");
|
|
1903
1923
|
}
|
|
1904
1924
|
const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index2) => index2.unique).map((index2) => index2.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
|
|
1905
1925
|
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");
|
|
@@ -2004,27 +2024,48 @@ var canonicalDefinitionSchema = z.object({
|
|
|
2004
2024
|
const visiting = /* @__PURE__ */ new Set();
|
|
2005
2025
|
const visited = /* @__PURE__ */ new Set();
|
|
2006
2026
|
let cycleReported = false;
|
|
2007
|
-
const visit = (
|
|
2008
|
-
if (visited.has(
|
|
2009
|
-
if (visiting.has(
|
|
2027
|
+
const visit = (name2, path) => {
|
|
2028
|
+
if (visited.has(name2) || cycleReported) return;
|
|
2029
|
+
if (visiting.has(name2)) {
|
|
2010
2030
|
cycleReported = true;
|
|
2011
2031
|
ctx.addIssue({
|
|
2012
2032
|
code: "custom",
|
|
2013
2033
|
path: ["views"],
|
|
2014
|
-
message: `view dependencies form a cycle: ${[...path,
|
|
2034
|
+
message: `view dependencies form a cycle: ${[...path, name2].join(" -> ")}`
|
|
2015
2035
|
});
|
|
2016
2036
|
return;
|
|
2017
2037
|
}
|
|
2018
|
-
visiting.add(
|
|
2019
|
-
const view = viewByName.get(
|
|
2038
|
+
visiting.add(name2);
|
|
2039
|
+
const view = viewByName.get(name2);
|
|
2020
2040
|
for (const dependency of view?.dependencies ?? []) {
|
|
2021
|
-
if (viewByName.has(dependency)) visit(dependency, [...path,
|
|
2041
|
+
if (viewByName.has(dependency)) visit(dependency, [...path, name2]);
|
|
2022
2042
|
}
|
|
2023
|
-
visiting.delete(
|
|
2024
|
-
visited.add(
|
|
2043
|
+
visiting.delete(name2);
|
|
2044
|
+
visited.add(name2);
|
|
2025
2045
|
};
|
|
2026
2046
|
for (const view of views) visit(view.view, []);
|
|
2027
2047
|
});
|
|
2048
|
+
var canonicalDefinitionSchema = localDefinitionSchema.safeExtend({
|
|
2049
|
+
/** Flattened, pinned snapshots. Imported relations never participate in this namespace's DDL. */
|
|
2050
|
+
imports: z.array(localDefinitionSchema).max(32).optional()
|
|
2051
|
+
});
|
|
2052
|
+
function canonicalRelations(definition) {
|
|
2053
|
+
const views = (definition.views ?? []).map((view) => ({
|
|
2054
|
+
table: view.view,
|
|
2055
|
+
...view.description !== void 0 ? { description: view.description } : {},
|
|
2056
|
+
columns: view.columns,
|
|
2057
|
+
...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
|
|
2058
|
+
links: view.links,
|
|
2059
|
+
indexes: [],
|
|
2060
|
+
// A resolved table's write policies (evaluated against the resolved row)
|
|
2061
|
+
// ride alongside the view's read policy.
|
|
2062
|
+
policies: { read: view.policies.read, ...view.resolved?.policies ?? {} },
|
|
2063
|
+
actions: [],
|
|
2064
|
+
...view.resolved !== void 0 ? { resolved: view.resolved } : {},
|
|
2065
|
+
...view.internal ? { internal: true } : {}
|
|
2066
|
+
}));
|
|
2067
|
+
return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
|
|
2068
|
+
}
|
|
2028
2069
|
function normalizeCanonical(definition) {
|
|
2029
2070
|
const sortByName = (items) => [...items].sort((a, b) => a.name.localeCompare(b.name));
|
|
2030
2071
|
const normalized = {
|
|
@@ -2051,6 +2092,11 @@ function normalizeCanonical(definition) {
|
|
|
2051
2092
|
dependencies: [...view.dependencies].sort()
|
|
2052
2093
|
}));
|
|
2053
2094
|
}
|
|
2095
|
+
if (definition.imports?.length) {
|
|
2096
|
+
normalized.imports = definition.imports.map(normalizeCanonical).sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
2097
|
+
} else {
|
|
2098
|
+
delete normalized.imports;
|
|
2099
|
+
}
|
|
2054
2100
|
return normalized;
|
|
2055
2101
|
}
|
|
2056
2102
|
|
|
@@ -2175,8 +2221,8 @@ var literal = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
|
2175
2221
|
var physical = ({ source, property }) => source.table.columns[property].columnNameFor(property);
|
|
2176
2222
|
function candidateSql({ source, column, pk, pgType }) {
|
|
2177
2223
|
const a = ident(`s_${source.name}`);
|
|
2178
|
-
const
|
|
2179
|
-
const value = `CAST(${a}.${ident(
|
|
2224
|
+
const name2 = physical({ source, property: column });
|
|
2225
|
+
const value = `CAST(${a}.${ident(name2)} AS ${pgType})`;
|
|
2180
2226
|
if (source.kind === "etl") {
|
|
2181
2227
|
return {
|
|
2182
2228
|
source: source.name,
|
|
@@ -2186,9 +2232,9 @@ function candidateSql({ source, column, pk, pgType }) {
|
|
|
2186
2232
|
by: "CAST(NULL AS text)"
|
|
2187
2233
|
};
|
|
2188
2234
|
}
|
|
2189
|
-
const at = `${a}.${ident(cellColumnName(
|
|
2235
|
+
const at = `${a}.${ident(cellColumnName(name2, "at"))}`;
|
|
2190
2236
|
const tombstoned2 = source.kind === "prefetch" ? ` AND NOT ${a}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName)}` : "";
|
|
2191
|
-
return { source: source.name, value, has: `(${at} IS NOT NULL${tombstoned2})`, at, by: `${a}.${ident(cellColumnName(
|
|
2237
|
+
return { source: source.name, value, has: `(${at} IS NOT NULL${tombstoned2})`, at, by: `${a}.${ident(cellColumnName(name2, "by"))}` };
|
|
2192
2238
|
}
|
|
2193
2239
|
function outranks({ i, j, key, direction, inclusive }) {
|
|
2194
2240
|
const ki = i[key];
|
|
@@ -2281,7 +2327,7 @@ function buildResolvedQuery({ context, plan }) {
|
|
|
2281
2327
|
plan.rules.forEach((rule, index) => {
|
|
2282
2328
|
const canonical = plan.columns[rule.column].toCanonicalColumn(rule.column);
|
|
2283
2329
|
const pgType = typeof canonical.type === "string" ? PG_TYPE[canonical.type] : `${schema === void 0 ? "" : `${ident(schema)}.`}${ident(canonical.type.enum)}`;
|
|
2284
|
-
const candidates = rule.candidates.map((
|
|
2330
|
+
const candidates = rule.candidates.map((name2) => candidateSql({ source: sourceByName.get(name2), column: rule.column, pk: plan.pk, pgType }));
|
|
2285
2331
|
cells.set(rule.column, rule.comparator === "custom" ? customCell({ candidates, orderBy: rule.orderBy, index }) : builtinCell({ comparator: rule.comparator, candidates }));
|
|
2286
2332
|
});
|
|
2287
2333
|
const laterals = [...cells.values()].flatMap((cell) => cell.lateral ? [sql.raw(cell.lateral)] : []);
|
|
@@ -2302,7 +2348,7 @@ function buildResolvedQuery({ context, plan }) {
|
|
|
2302
2348
|
const system = RESOLVED_VIEW_SYSTEM_COLUMNS;
|
|
2303
2349
|
selected[system.writebackStatus.name] = sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.columnName)}`).as(system.writebackStatus.columnName);
|
|
2304
2350
|
selected[system.writebackError.name] = sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.columnName)}`).as(system.writebackError.columnName);
|
|
2305
|
-
const hidden = plan.deletedBy.map((
|
|
2351
|
+
const hidden = plan.deletedBy.map((name2) => `NOT COALESCE(${alias(name2)}.${ident(tombstoned2)}, false)`);
|
|
2306
2352
|
const query = qb.select(selected).from(from);
|
|
2307
2353
|
return hidden.length > 0 ? query.where(sql.raw(hidden.join(" AND "))) : query;
|
|
2308
2354
|
}
|
|
@@ -2328,12 +2374,12 @@ var SYSTEM_VIEW_COLUMNS = {
|
|
|
2328
2374
|
[RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.columnName),
|
|
2329
2375
|
[RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.columnName)
|
|
2330
2376
|
};
|
|
2331
|
-
function defineResolvedTable(
|
|
2377
|
+
function defineResolvedTable(name2, columns, config) {
|
|
2332
2378
|
const file = activeAuthoringContext()?.currentFile;
|
|
2333
2379
|
const fail = (message) => {
|
|
2334
|
-
throw new OrmDefinitionError(`resolved table "${
|
|
2380
|
+
throw new OrmDefinitionError(`resolved table "${name2}": ${message}`, file);
|
|
2335
2381
|
};
|
|
2336
|
-
if (!IDENTIFIER_PATTERN.test(
|
|
2382
|
+
if (!IDENTIFIER_PATTERN.test(name2)) fail(`name must match ${IDENTIFIER_PATTERN}`);
|
|
2337
2383
|
for (const property of Object.keys(columns)) {
|
|
2338
2384
|
if (RESERVED_RESOLVED_COLUMN_NAMES.has(property) || RESERVED_RESOLVED_COLUMN_NAMES.has(columns[property].columnNameFor(property))) {
|
|
2339
2385
|
fail(`column "${property}" collides with a reserved system column`);
|
|
@@ -2352,7 +2398,7 @@ function defineResolvedTable(name, columns, config) {
|
|
|
2352
2398
|
const sourceEntries = Object.entries(config.sources);
|
|
2353
2399
|
if (sourceEntries.filter(([, source]) => source.kind === "manual").length !== 1) fail("declare exactly one manualSource()");
|
|
2354
2400
|
const plans = sourceEntries.map(
|
|
2355
|
-
([sourceName, input]) => planSource({ viewName:
|
|
2401
|
+
([sourceName, input]) => planSource({ viewName: name2, sourceName, input, columns, pk, pkBuilder, dataColumns, fail })
|
|
2356
2402
|
);
|
|
2357
2403
|
const rules = planRules({ config, plans, dataColumns, fail });
|
|
2358
2404
|
const deletedBy = [...config.rows?.deletedBy ?? []];
|
|
@@ -2386,23 +2432,23 @@ function defineResolvedTable(name, columns, config) {
|
|
|
2386
2432
|
policies: { read }
|
|
2387
2433
|
};
|
|
2388
2434
|
if (!config.materialize) {
|
|
2389
|
-
return defineView(
|
|
2435
|
+
return defineView(name2, typedColumns, { ...surface, query: (context) => buildResolvedQuery({ context, plan: queryPlan }), resolved });
|
|
2390
2436
|
}
|
|
2391
2437
|
const materialize = config.materialize === true ? {} : config.materialize;
|
|
2392
|
-
const computeName = `${
|
|
2393
|
-
const tableName = `${
|
|
2438
|
+
const computeName = `${name2}__compute`;
|
|
2439
|
+
const tableName = `${name2}__materialized`;
|
|
2394
2440
|
const compute = defineView(computeName, typedColumns, {
|
|
2395
2441
|
policies: { read },
|
|
2396
2442
|
query: (context) => buildResolvedQuery({ context, plan: queryPlan }),
|
|
2397
2443
|
internal: true
|
|
2398
2444
|
});
|
|
2399
2445
|
const table = defineTable(tableName, viewColumns, {
|
|
2400
|
-
description: `Materialized rows of resolved table "${
|
|
2446
|
+
description: `Materialized rows of resolved table "${name2}". System-maintained from "${computeName}".`,
|
|
2401
2447
|
internal: true,
|
|
2402
|
-
materializes:
|
|
2448
|
+
materializes: name2,
|
|
2403
2449
|
...materialize.indexes !== void 0 ? { indexes: materialize.indexes } : {}
|
|
2404
2450
|
});
|
|
2405
|
-
return defineView(
|
|
2451
|
+
return defineView(name2, typedColumns, {
|
|
2406
2452
|
...surface,
|
|
2407
2453
|
query: (context) => {
|
|
2408
2454
|
const handle = context.relation(table);
|
|
@@ -2429,7 +2475,7 @@ function planRules({ config, plans, dataColumns, fail }) {
|
|
|
2429
2475
|
return dataColumns.map((column) => {
|
|
2430
2476
|
const explicit = config.resolve?.[column];
|
|
2431
2477
|
const comparator = explicit ?? fallback;
|
|
2432
|
-
const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((
|
|
2478
|
+
const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((name2) => plans.find((plan) => plan.name === name2)?.columns.includes(column));
|
|
2433
2479
|
for (const candidate of candidates) {
|
|
2434
2480
|
const plan = plans.find((entry) => entry.name === candidate);
|
|
2435
2481
|
if (!plan) fail(`column "${column}" names unknown source "${candidate}"`);
|
|
@@ -2508,6 +2554,72 @@ function stableJson(value) {
|
|
|
2508
2554
|
return `{${entries.join(",")}}`;
|
|
2509
2555
|
}
|
|
2510
2556
|
|
|
2557
|
+
// src/canonical/composition.ts
|
|
2558
|
+
function localContract(definition) {
|
|
2559
|
+
return normalizeCanonical(localDefinitionSchema.parse(definition));
|
|
2560
|
+
}
|
|
2561
|
+
function flattenNamespaceImports(inputs) {
|
|
2562
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
2563
|
+
const visit = (value, depth) => {
|
|
2564
|
+
if (depth > 16 || definitions.size > 32) throw new OrmDefinitionError("namespace import graph is too large");
|
|
2565
|
+
const definition = localContract(localDefinitionSchema.parse(value));
|
|
2566
|
+
const previous = definitions.get(definition.namespace);
|
|
2567
|
+
if (previous) {
|
|
2568
|
+
if (stableJson(previous) !== stableJson(definition)) throw new OrmDefinitionError(`conflicting contracts for namespace '${definition.namespace}'`);
|
|
2569
|
+
}
|
|
2570
|
+
definitions.set(definition.namespace, definition);
|
|
2571
|
+
if (typeof value === "object" && value !== null && "imports" in value && Array.isArray(value.imports)) {
|
|
2572
|
+
for (const dependency of value.imports) visit(dependency, depth + 1);
|
|
2573
|
+
}
|
|
2574
|
+
};
|
|
2575
|
+
for (const input of inputs) visit(input, 0);
|
|
2576
|
+
return [...definitions.values()].sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
2577
|
+
}
|
|
2578
|
+
function composeNamespace(definition) {
|
|
2579
|
+
const all = [localContract(definition), ...definition.imports ?? []];
|
|
2580
|
+
const owners = /* @__PURE__ */ new Map();
|
|
2581
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
2582
|
+
const enums = /* @__PURE__ */ new Map();
|
|
2583
|
+
for (const owner of all) {
|
|
2584
|
+
if (namespaces.has(owner.namespace)) throw new OrmDefinitionError(`duplicate imported namespace '${owner.namespace}'`);
|
|
2585
|
+
namespaces.add(owner.namespace);
|
|
2586
|
+
for (const value of owner.enums) {
|
|
2587
|
+
const previous = enums.get(value.name);
|
|
2588
|
+
if (previous && stableJson(previous) !== stableJson(value)) {
|
|
2589
|
+
throw new OrmDefinitionError(`conflicting imported enum '${value.name}'`);
|
|
2590
|
+
}
|
|
2591
|
+
enums.set(value.name, value);
|
|
2592
|
+
}
|
|
2593
|
+
for (const relation of canonicalRelations(owner)) {
|
|
2594
|
+
if (owners.has(relation.table)) throw new OrmDefinitionError(`ambiguous imported relation '${relation.table}'`);
|
|
2595
|
+
owners.set(relation.table, owner);
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
for (const owner of definition.imports?.length ? all : []) {
|
|
2599
|
+
for (const relation of canonicalRelations(owner)) {
|
|
2600
|
+
for (const link of relation.links) {
|
|
2601
|
+
if (link.kind === "toPlatformUser") continue;
|
|
2602
|
+
const target = owners.get(link.target ?? "");
|
|
2603
|
+
if (!target || target.namespace !== (link.namespace ?? owner.namespace)) {
|
|
2604
|
+
throw new OrmDefinitionError(`link '${owner.namespace}/${relation.table}.${link.name}' has no matching imported target`);
|
|
2605
|
+
}
|
|
2606
|
+
if (target.namespace !== owner.namespace && !target.exports?.[link.target ?? ""]) {
|
|
2607
|
+
throw new OrmDefinitionError(`target '${target.namespace}/${link.target}' is not exported`);
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
return {
|
|
2613
|
+
owners,
|
|
2614
|
+
definition: {
|
|
2615
|
+
...localContract(definition),
|
|
2616
|
+
tables: all.flatMap((item) => item.tables),
|
|
2617
|
+
views: all.flatMap((item) => item.views ?? []),
|
|
2618
|
+
enums: [...enums.values()]
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2511
2623
|
// src/authoring/namespace.ts
|
|
2512
2624
|
var NAMESPACE_CONFIG_BRAND = "seq-namespace-config";
|
|
2513
2625
|
function isSeqNamespaceConfig(value) {
|
|
@@ -2523,6 +2635,8 @@ function defineNamespace(input) {
|
|
|
2523
2635
|
namespace: input.namespace,
|
|
2524
2636
|
description: input.description,
|
|
2525
2637
|
capabilities: input.capabilities ?? {},
|
|
2638
|
+
imports: input.imports,
|
|
2639
|
+
exports: input.exports,
|
|
2526
2640
|
file
|
|
2527
2641
|
};
|
|
2528
2642
|
registerNamespaceConfig(config);
|
|
@@ -2567,15 +2681,15 @@ function serializeNamespace(input) {
|
|
|
2567
2681
|
}
|
|
2568
2682
|
const relationsByName = /* @__PURE__ */ new Map();
|
|
2569
2683
|
for (const relation of [...tables, ...views]) {
|
|
2570
|
-
const
|
|
2571
|
-
const existing = relationsByName.get(
|
|
2684
|
+
const name2 = relationName(relation);
|
|
2685
|
+
const existing = relationsByName.get(name2);
|
|
2572
2686
|
if (existing) {
|
|
2573
2687
|
throw new OrmDefinitionError(
|
|
2574
|
-
`duplicate relation "${
|
|
2688
|
+
`duplicate relation "${name2}" (defined as ${relationKind(existing)} in ${describeFile(existing)} and ${relationKind(relation)} in ${describeFile(relation)})`,
|
|
2575
2689
|
relation.file
|
|
2576
2690
|
);
|
|
2577
2691
|
}
|
|
2578
|
-
relationsByName.set(
|
|
2692
|
+
relationsByName.set(name2, relation);
|
|
2579
2693
|
}
|
|
2580
2694
|
const enumsByName = /* @__PURE__ */ new Map();
|
|
2581
2695
|
for (const ref of enums) {
|
|
@@ -2616,6 +2730,8 @@ function serializeNamespace(input) {
|
|
|
2616
2730
|
};
|
|
2617
2731
|
if (config.description !== void 0) candidate.description = config.description;
|
|
2618
2732
|
if (canonicalViews.length > 0) candidate.views = canonicalViews;
|
|
2733
|
+
if (config.imports?.length) candidate.imports = flattenNamespaceImports(config.imports);
|
|
2734
|
+
if (config.exports) candidate.exports = config.exports;
|
|
2619
2735
|
let parsed;
|
|
2620
2736
|
try {
|
|
2621
2737
|
parsed = canonicalDefinitionSchema.parse(candidate);
|
|
@@ -2625,7 +2741,9 @@ function serializeNamespace(input) {
|
|
|
2625
2741
|
}
|
|
2626
2742
|
throw error;
|
|
2627
2743
|
}
|
|
2628
|
-
|
|
2744
|
+
const normalized = normalizeCanonical(parsed);
|
|
2745
|
+
if (normalized.imports?.length) composeNamespace(normalized);
|
|
2746
|
+
return normalized;
|
|
2629
2747
|
}
|
|
2630
2748
|
function computeCanonicalHash(definition) {
|
|
2631
2749
|
return createHash("sha256").update(HASH_DOMAIN).update(stableJson(definition)).digest("hex");
|
|
@@ -2639,6 +2757,7 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
|
|
|
2639
2757
|
policies: {},
|
|
2640
2758
|
actions: []
|
|
2641
2759
|
};
|
|
2760
|
+
if (table.config.displayName !== void 0) candidate.displayName = table.config.displayName;
|
|
2642
2761
|
if (table.config.description !== void 0) candidate.description = table.config.description;
|
|
2643
2762
|
if (table.config.renamedFrom !== void 0) candidate.renamedFrom = table.config.renamedFrom;
|
|
2644
2763
|
if (table.source !== void 0) candidate.source = table.source;
|
|
@@ -2722,6 +2841,9 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
|
|
|
2722
2841
|
if (table.config.foreignKeys?.length) {
|
|
2723
2842
|
if (table.source) throw new OrmDefinitionError(`synced table "${table.tableName}" cannot enforce foreign keys`, table.file);
|
|
2724
2843
|
candidate.foreignKeys = table.config.foreignKeys.map((fk) => {
|
|
2844
|
+
if (typeof fk.target === "object" && fk.target.kind === "seq-external-object") {
|
|
2845
|
+
throw new OrmDefinitionError("physical foreign keys require a local target; use a cross-namespace ORM link", table.file);
|
|
2846
|
+
}
|
|
2725
2847
|
const target = resolveRelationRef({ ref: fk.target, fail: (message) => {
|
|
2726
2848
|
throw new OrmDefinitionError(message, table.file);
|
|
2727
2849
|
} });
|
|
@@ -2892,6 +3014,19 @@ function buildCanonicalLink({
|
|
|
2892
3014
|
...link.description !== void 0 ? { description: link.description } : {}
|
|
2893
3015
|
};
|
|
2894
3016
|
}
|
|
3017
|
+
if (typeof link.target === "object" && link.target?.kind === "seq-external-object") {
|
|
3018
|
+
if (link.enforce) return fail("cannot enforce a cross-namespace physical foreign key; use an ORM link without enforce");
|
|
3019
|
+
return {
|
|
3020
|
+
name: linkName,
|
|
3021
|
+
kind: link.linkKind,
|
|
3022
|
+
namespace: link.target.namespace,
|
|
3023
|
+
target: link.target.table,
|
|
3024
|
+
on: link.linkKind === "toOne" ? resolveColumnRef({ relation, ref: link.on, role: "link on" }) : link.on,
|
|
3025
|
+
...link.references !== void 0 ? { references: link.linkKind === "toMany" ? resolveColumnRef({ relation, ref: link.references, role: "link references" }) : link.references } : {},
|
|
3026
|
+
enforce: false,
|
|
3027
|
+
...link.description !== void 0 ? { description: link.description } : {}
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
2895
3030
|
const target = resolveRelationRef({ ref: link.target, fail });
|
|
2896
3031
|
const targetName = relationName(target);
|
|
2897
3032
|
if (!relationsByName.has(targetName)) {
|
|
@@ -2948,11 +3083,11 @@ function assertAcyclicViewDependencies(views) {
|
|
|
2948
3083
|
views.map((view) => [view.view, new Set(view.dependencies.filter((dependency) => viewNames.has(dependency)))])
|
|
2949
3084
|
);
|
|
2950
3085
|
while (remaining.size > 0) {
|
|
2951
|
-
const ready = [...remaining.entries()].filter(([, dependencies]) => [...dependencies].every((dependency) => !remaining.has(dependency))).map(([
|
|
3086
|
+
const ready = [...remaining.entries()].filter(([, dependencies]) => [...dependencies].every((dependency) => !remaining.has(dependency))).map(([name2]) => name2);
|
|
2952
3087
|
if (ready.length === 0) {
|
|
2953
3088
|
throw new OrmDefinitionError(`view dependencies form a cycle among: ${[...remaining.keys()].sort().join(", ")}`);
|
|
2954
3089
|
}
|
|
2955
|
-
for (const
|
|
3090
|
+
for (const name2 of ready) remaining.delete(name2);
|
|
2956
3091
|
}
|
|
2957
3092
|
}
|
|
2958
3093
|
function toCanonicalPolicy(policy) {
|
|
@@ -2998,8 +3133,8 @@ function syncedContractSurface({
|
|
|
2998
3133
|
namespace,
|
|
2999
3134
|
table,
|
|
3000
3135
|
columns: Object.fromEntries(
|
|
3001
|
-
Object.entries(columns).map(([
|
|
3002
|
-
|
|
3136
|
+
Object.entries(columns).map(([name2, column]) => [
|
|
3137
|
+
name2,
|
|
3003
3138
|
{ type: column.type, nullable: column.nullable, key: column.key }
|
|
3004
3139
|
])
|
|
3005
3140
|
),
|
|
@@ -3020,7 +3155,7 @@ function extractOrmSyncedTableContract({
|
|
|
3020
3155
|
}
|
|
3021
3156
|
if (table.source === void 0) {
|
|
3022
3157
|
throw new OrmDefinitionError(
|
|
3023
|
-
`table "${table.table}" is not a synced table \u2014 extractOrmSyncedTableContract requires source
|
|
3158
|
+
`table "${table.table}" is not a synced table \u2014 extractOrmSyncedTableContract requires a qualified source`
|
|
3024
3159
|
);
|
|
3025
3160
|
}
|
|
3026
3161
|
if (table.columns.length === 0) {
|
|
@@ -3167,7 +3302,54 @@ function purgeModuleCache(root) {
|
|
|
3167
3302
|
if (key.startsWith(prefix)) delete require2.cache[key];
|
|
3168
3303
|
}
|
|
3169
3304
|
}
|
|
3305
|
+
var sqlScalarSchema = z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]);
|
|
3306
|
+
var name = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(63).refine((value) => !["__proto__", "prototype", "constructor"].includes(value), "reserved name");
|
|
3307
|
+
var base = z.object({
|
|
3308
|
+
name,
|
|
3309
|
+
namespace: name,
|
|
3310
|
+
language: z.literal("sql"),
|
|
3311
|
+
kind: z.literal("query"),
|
|
3312
|
+
requires: z.object({ capability: z.string().min(1).max(128) }).strict(),
|
|
3313
|
+
ontology: z.object({ read: z.array(z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*\/[A-Za-z_][A-Za-z0-9_]*$/)).min(1).max(256) }).strict(),
|
|
3314
|
+
maxRows: z.number().int().min(1).max(1e4).default(1e3)
|
|
3315
|
+
});
|
|
3316
|
+
var fixed = base.extend({
|
|
3317
|
+
parameters: z.record(name, z.object({ type: sqlScalarSchema, required: z.boolean().default(true) }).strict()).default({}),
|
|
3318
|
+
sql: z.string().min(1).max(65536)
|
|
3319
|
+
}).strict();
|
|
3320
|
+
var runtime = base.extend({ mode: z.literal("runtime") }).strict();
|
|
3321
|
+
var sqlOperationDefinitionSchema = z.union([fixed, runtime]);
|
|
3322
|
+
function defineOperation(input) {
|
|
3323
|
+
return sqlOperationDefinitionSchema.parse(input);
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
// src/authoring/catalog.ts
|
|
3327
|
+
var SEGMENT = /^[^./\\\s][^/\\]*[^./\\\s]$|^[^./\\\s]$/;
|
|
3328
|
+
function validateNamespaceCatalog(catalog, file) {
|
|
3329
|
+
const paths = /* @__PURE__ */ new Set([""]);
|
|
3330
|
+
function visit(folders, parent) {
|
|
3331
|
+
for (const [name2, children] of Object.entries(folders)) {
|
|
3332
|
+
if (!SEGMENT.test(name2) || ["__proto__", "constructor", "prototype"].includes(name2)) {
|
|
3333
|
+
throw new OrmDefinitionError(`invalid catalog folder name "${name2}"`, file);
|
|
3334
|
+
}
|
|
3335
|
+
const path = parent ? `${parent}/${name2}` : name2;
|
|
3336
|
+
paths.add(path);
|
|
3337
|
+
if (children !== null) {
|
|
3338
|
+
if (typeof children !== "object" || Array.isArray(children)) {
|
|
3339
|
+
throw new OrmDefinitionError(`catalog folder "${path}" must contain folders or null`, file);
|
|
3340
|
+
}
|
|
3341
|
+
visit(children, path);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
visit(catalog.folders, "");
|
|
3346
|
+
for (const [object, path] of Object.entries(catalog.objects)) {
|
|
3347
|
+
if (!object || !paths.has(path)) {
|
|
3348
|
+
throw new OrmDefinitionError(`catalog object "${object}" references missing folder "${path}"`, file);
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3170
3352
|
|
|
3171
|
-
export { COLUMN_BRAND, COLUMN_DEFAULT_FNS, ColumnBuilder, ENUM_BRAND, LINK_BRAND, NAMESPACE_CONFIG_BRAND, NAMESPACE_CONFIG_FILE, ORM_TO_PIPELINE_TYPE, SCHEMA_DIR, SYNCED_CONTRACT_HASH_DOMAIN, TABLE_BRAND, VIEW_BRAND, activeAuthoringContext, beginAuthoringContext, computeCanonicalHash, computeSyncedContractHash, custom, defineEnum, defineNamespace, defineResolvedTable, defineSyncedTable, defineTable, defineView, deriveColumnName, endAuthoringContext, etlSource, extractOrmSyncedTableContract, highest, isColumnBuilder, isEnumRef, isSeqLink, isSeqNamespaceConfig, isSeqTable, isSeqView, latest, loadNamespaceFromDir, lowest, managedFn, manualSource, nonNullFirst, overlayWinsUnion, overrideRowMutation, prefetchSource, priority, revertOverlayMutation, serializeNamespace, syncedContractSurface, t, toMany, toOne, toPlatformUser };
|
|
3353
|
+
export { COLUMN_BRAND, COLUMN_DEFAULT_FNS, ColumnBuilder, ENUM_BRAND, LINK_BRAND, NAMESPACE_CONFIG_BRAND, NAMESPACE_CONFIG_FILE, ORM_TO_PIPELINE_TYPE, SCHEMA_DIR, SYNCED_CONTRACT_HASH_DOMAIN, TABLE_BRAND, VIEW_BRAND, activeAuthoringContext, beginAuthoringContext, computeCanonicalHash, computeSyncedContractHash, custom, defineEnum, defineNamespace, defineOperation, defineResolvedTable, defineSyncedTable, defineTable, defineView, deriveColumnName, endAuthoringContext, etlSource, extractOrmSyncedTableContract, highest, isColumnBuilder, isEnumRef, isSeqLink, isSeqNamespaceConfig, isSeqTable, isSeqView, latest, loadNamespaceFromDir, lowest, managedFn, manualSource, nonNullFirst, objectRef, overlayWinsUnion, overrideRowMutation, prefetchSource, priority, revertOverlayMutation, serializeNamespace, syncedContractSurface, t, toMany, toOne, toPlatformUser, validateNamespaceCatalog };
|
|
3172
3354
|
//# sourceMappingURL=authoring.js.map
|
|
3173
3355
|
//# sourceMappingURL=authoring.js.map
|