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