@sequenceholdings/orm 2.0.0 → 2.1.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.
Files changed (50) hide show
  1. package/README.md +57 -2
  2. package/dist/authoring.cjs +122 -15
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +20 -3
  5. package/dist/authoring.d.ts +20 -3
  6. package/dist/authoring.js +122 -15
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +83 -9
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +47 -1
  11. package/dist/canonical.d.ts +47 -1
  12. package/dist/canonical.js +83 -10
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +721 -132
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +723 -134
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.cjs.map +1 -1
  19. package/dist/client.js.map +1 -1
  20. package/dist/compiler.cjs +482 -96
  21. package/dist/compiler.cjs.map +1 -1
  22. package/dist/compiler.d.cts +34 -2
  23. package/dist/compiler.d.ts +34 -2
  24. package/dist/compiler.js +483 -97
  25. package/dist/compiler.js.map +1 -1
  26. package/dist/index.cjs +414 -70
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +266 -9
  29. package/dist/index.d.ts +266 -9
  30. package/dist/index.js +404 -72
  31. package/dist/index.js.map +1 -1
  32. package/dist/ir.cjs +146 -12
  33. package/dist/ir.cjs.map +1 -1
  34. package/dist/ir.d.cts +161 -5
  35. package/dist/ir.d.ts +161 -5
  36. package/dist/ir.js +144 -13
  37. package/dist/ir.js.map +1 -1
  38. package/dist/{naming-CTvWSRTf.d.ts → naming-DCn94LJ4.d.ts} +15 -1
  39. package/dist/{naming-DRyinU4u.d.cts → naming-DPglwzkB.d.cts} +15 -1
  40. package/dist/predicate-v2.cjs +130 -11
  41. package/dist/predicate-v2.cjs.map +1 -1
  42. package/dist/predicate-v2.js +130 -11
  43. package/dist/predicate-v2.js.map +1 -1
  44. package/dist/sdl.cjs +460 -13
  45. package/dist/sdl.cjs.map +1 -1
  46. package/dist/sdl.d.cts +1 -1
  47. package/dist/sdl.d.ts +1 -1
  48. package/dist/sdl.js +459 -14
  49. package/dist/sdl.js.map +1 -1
  50. package/package.json +3 -3
package/README.md CHANGED
@@ -5,12 +5,18 @@ namespaces. You define tables, policies, and named GraphQL operations in source
5
5
  control; the platform compiles them into persisted operations and enforces the
6
6
  caller's access on every execution.
7
7
 
8
+ Sequence ORM is a workflow-agnostic platform primitive. The compiler and
9
+ runtime own generic schema, policy, migration, and execution contracts;
10
+ deployed namespaces own domain tables and operations. Primitive code must not
11
+ branch on a namespace or product identity. See
12
+ [Platform primitive boundaries](../../../docs/platform/platform-primitives.md).
13
+
8
14
  ## Install
9
15
 
10
16
  Install the CLI and SDK together:
11
17
 
12
18
  ```bash
13
- pnpm add -g @sequenceholdings/studio-cli@0.1.27 @sequenceholdings/orm@2.0.0
19
+ pnpm add -g @sequenceholdings/studio-cli@0.1.35 @sequenceholdings/orm@2.1.0
14
20
  ```
15
21
 
16
22
  Create a namespace project:
@@ -32,7 +38,12 @@ seq-studio orm apply . -e <environment-name>
32
38
  ```
33
39
 
34
40
  `generate` writes `schema.graphql`, `operations.manifest.json`, cache policies,
35
- and typed consumer modules. Commit generated outputs and migrations with the
41
+ and typed consumer modules. Generated SDL may contain up to 16 MiB of UTF-8
42
+ text for the complete namespace; individual authored files retain a 1 MiB cap.
43
+ Registration and reviewed migration exports are bounded to 64 MiB including
44
+ canonical metadata, snapshots, documents, and migration statements.
45
+
46
+ Commit generated outputs and migrations with the
36
47
  source that produced them.
37
48
 
38
49
  Use `apply --dry-run` to rehearse a migration against a disposable copy of the
@@ -41,6 +52,46 @@ namespace, which is useful for deployment checks.
41
52
 
42
53
  ## Execute persisted operations
43
54
 
55
+ ### Guard dependent writes
56
+
57
+ Mutation roots run in one transaction. To require a conditional write to
58
+ succeed before committing dependent roots, add a fixed row-count assertion:
59
+
60
+ ```graphql
61
+ mutation SaveDeal($id: uuid!, $revision: Int!, $title: String!) {
62
+ updateDeals(
63
+ where: { id: { _eq: $id }, revision: { _eq: $revision } }
64
+ _set: { title: $title }
65
+ _inc: { revision: 1 }
66
+ ) @assertAffectedRows(exactly: 1) {
67
+ returning { id revision }
68
+ }
69
+ }
70
+ ```
71
+
72
+ This example assumes a `deals` table with `id`, `title`, and integer `revision`
73
+ columns and suitable write policies. The assertion uses the actual SQL write
74
+ count even when `affectedRows` is not selected. A mismatch rolls back **all**
75
+ roots and the operation journal with HTTP 409 `MUTATION_PRECONDITION_FAILED`.
76
+ Missing, policy-hidden, and stale matches receive the same generic error.
77
+ Ordinary mutations without this directive keep zero-match success semantics.
78
+
79
+ `exactly` must be a literal integer from 0 through 2147483647. It cannot be a
80
+ caller variable. Only mutation root fields may carry the directive, and a
81
+ guarded root cannot also use `@include` or `@skip`. Policy and constraint
82
+ failures retain their existing errors. This does not add automatic retries:
83
+ when the domain permits retry, reread authorized state and recompute the write.
84
+ Every writer affecting the invariant must participate in the revision protocol.
85
+
86
+ Guarded operations use **IR v2**; older runtimes reject them. Upgrade the host
87
+ compiler and executor together before registering guarded documents, regenerate
88
+ SDL with the matching SDK, and only then activate consumers. Existing unguarded
89
+ operations keep their IR v1 and operation hashes. This source addition requires
90
+ a matching SDK release and host rollout; it is not a claim that the published
91
+ Preview version already supports it.
92
+
93
+ ### Execute by name
94
+
44
95
  Operations execute by their generated manifest name, never by sending GraphQL
45
96
  text or SQL:
46
97
 
@@ -53,6 +104,10 @@ seq-studio orm execute GetOpenDeals . \
53
104
  `variables.json` must contain a JSON object. The platform validates variables,
54
105
  operation requirements, and row policies as the signed-in caller.
55
106
 
107
+ Execute uses the `/_data/v2` endpoint by default. Set
108
+ `SEQUENCE_ORM_ATLAS_EXECUTE_V2=1` to opt into Atlas's `/api/data/v2` endpoint.
109
+ This client rollout switch does not bypass server feature flags or authorization.
110
+
56
111
  ## Public exports
57
112
 
58
113
  - `@sequenceholdings/orm/authoring` — namespace, table, policy, and view DSL
@@ -33,6 +33,7 @@ var ColumnBuilder = class {
33
33
  hasDefault: false,
34
34
  defaultValue: void 0,
35
35
  defaultFn: void 0,
36
+ onUpdateFn: void 0,
36
37
  description: void 0,
37
38
  renamedFrom: void 0,
38
39
  cast: void 0,
@@ -68,6 +69,15 @@ var ColumnBuilder = class {
68
69
  this.state.defaultFn = void 0;
69
70
  return this;
70
71
  }
72
+ /**
73
+ * Stamp this column with Postgres `now()` on every UPDATE. Only valid on
74
+ * timestamptz/date; the compiler drops it from SetInput so callers cannot
75
+ * supply a value.
76
+ */
77
+ onUpdate(value) {
78
+ this.state.onUpdateFn = value;
79
+ return this;
80
+ }
71
81
  description(text2) {
72
82
  this.state.description = text2;
73
83
  return this;
@@ -111,6 +121,7 @@ var ColumnBuilder = class {
111
121
  };
112
122
  if (this.state.hasDefault) column.default = this.state.defaultValue ?? null;
113
123
  if (this.state.defaultFn !== void 0) column.defaultFn = this.state.defaultFn;
124
+ if (this.state.onUpdateFn !== void 0) column.onUpdateFn = this.state.onUpdateFn;
114
125
  if (this.state.description !== void 0) column.description = this.state.description;
115
126
  if (this.state.renamedFrom !== void 0) column.renamedFrom = this.state.renamedFrom;
116
127
  if (this.state.cast !== void 0) column.cast = this.state.cast;
@@ -138,6 +149,7 @@ var t = {
138
149
  // src/constants.ts
139
150
  var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
140
151
  var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
152
+ var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
141
153
  var MAX_IDENTIFIER_LENGTH = 63;
142
154
  var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
143
155
  var HASH_DOMAIN = "sequence-orm-definitions\0";
@@ -240,10 +252,10 @@ function isSeqLink(value) {
240
252
  function isSeqTable(value) {
241
253
  return typeof value === "object" && value !== null && value.kind === TABLE_BRAND;
242
254
  }
243
- function assertIdentifier({ value, what, file }) {
244
- if (!IDENTIFIER_PATTERN.test(value) || value.length > MAX_IDENTIFIER_LENGTH) {
255
+ function assertIdentifier({ value, what, file, pattern = IDENTIFIER_PATTERN }) {
256
+ if (!pattern.test(value) || value.length > MAX_IDENTIFIER_LENGTH) {
245
257
  throw new OrmDefinitionError(
246
- `${what} "${value}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
258
+ `${what} "${value}" must match ${pattern} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
247
259
  file
248
260
  );
249
261
  }
@@ -264,7 +276,7 @@ function defineTable(name, columns, config = {}) {
264
276
  );
265
277
  }
266
278
  const columnName = builder.columnNameFor(propertyName);
267
- assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file });
279
+ assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
268
280
  const existing = seenColumnNames.get(columnName);
269
281
  if (existing !== void 0) {
270
282
  throw new OrmDefinitionError(
@@ -304,7 +316,8 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
304
316
  assertIdentifier({
305
317
  value: columnName,
306
318
  what: `synced table "${name}": column "${propertyName}" column name`,
307
- file
319
+ file,
320
+ pattern: COLUMN_IDENTIFIER_PATTERN
308
321
  });
309
322
  const existing = seenColumnNames.get(columnName);
310
323
  if (existing !== void 0) {
@@ -393,7 +406,7 @@ function defineView(name, columns, config) {
393
406
  );
394
407
  }
395
408
  const columnName = builder.columnNameFor(propertyName);
396
- if (!IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
409
+ if (!COLUMN_IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
397
410
  throw new OrmDefinitionError(`view "${name}": column "${propertyName}" has invalid column name "${columnName}"`, file);
398
411
  }
399
412
  const existing = seenColumnNames.get(columnName);
@@ -842,6 +855,7 @@ function scalarColumnType(table, property, helper) {
842
855
  var CANONICAL_FORMAT = 2;
843
856
  var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
844
857
  var identifier = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
858
+ var columnIdentifier = zod.z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
845
859
  var COLUMN_TYPES = [
846
860
  "uuid",
847
861
  "text",
@@ -862,18 +876,33 @@ var canonicalEnumSchema = zod.z.object({
862
876
  var canonicalColumnSchema = zod.z.object({
863
877
  /** Property name in TS (camelCase); columnName is the Postgres name. */
864
878
  name: zod.z.string().min(1).max(63),
865
- columnName: identifier,
879
+ columnName: columnIdentifier,
866
880
  type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier })]),
867
881
  nullable: zod.z.boolean().default(true),
868
882
  unique: zod.z.boolean().default(false),
869
883
  /** Literal default, or one of the allowlisted SQL functions. */
870
884
  default: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]).optional(),
871
885
  defaultFn: zod.z.enum(["gen_random_uuid()", "now()"]).optional(),
886
+ /**
887
+ * Stamp this column from the database clock on UPDATE (`now()`). The
888
+ * column stays insertable (defaults fill gaps) but is omitted from
889
+ * SetInput / settable so callers cannot backdate it.
890
+ */
891
+ onUpdateFn: zod.z.enum(["now()"]).optional(),
872
892
  description: zod.z.string().max(500).optional(),
873
893
  /** Migration hints — same semantics as v1. */
874
- renamedFrom: identifier.optional(),
894
+ renamedFrom: columnIdentifier.optional(),
875
895
  cast: zod.z.string().max(200).optional(),
876
896
  deprecated: zod.z.boolean().default(false)
897
+ }).superRefine((column, ctx) => {
898
+ if (column.onUpdateFn === void 0) return;
899
+ if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
900
+ ctx.addIssue({
901
+ code: "custom",
902
+ path: ["onUpdateFn"],
903
+ message: `onUpdateFn '${column.onUpdateFn}' is only valid on timestamptz/date columns`
904
+ });
905
+ }
877
906
  });
878
907
  var canonicalLinkSchema = zod.z.object({
879
908
  name: zod.z.string().min(1).max(63),
@@ -881,15 +910,15 @@ var canonicalLinkSchema = zod.z.object({
881
910
  /** Target table name (namespace-local). Absent for toPlatformUser. */
882
911
  target: identifier.optional(),
883
912
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
884
- on: identifier,
913
+ on: columnIdentifier,
885
914
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
886
- references: identifier.optional(),
915
+ references: columnIdentifier.optional(),
887
916
  /** enforce: true emits a real FK constraint (same-namespace toOne only). */
888
917
  enforce: zod.z.boolean().default(false),
889
918
  description: zod.z.string().max(500).optional()
890
919
  });
891
920
  var canonicalIndexSchema = zod.z.object({
892
- columns: zod.z.array(identifier).min(1),
921
+ columns: zod.z.array(columnIdentifier).min(1),
893
922
  unique: zod.z.boolean().default(false),
894
923
  name: identifier.optional()
895
924
  });
@@ -980,6 +1009,13 @@ var canonicalCowMutationSchema = zod.z.object({
980
1009
  }
981
1010
  }
982
1011
  });
1012
+ var canonicalForeignKeySchema = zod.z.object({
1013
+ name: identifier,
1014
+ columns: zod.z.array(columnIdentifier).min(1).max(8),
1015
+ target: identifier,
1016
+ references: zod.z.array(columnIdentifier).min(1).max(8),
1017
+ deferrable: zod.z.enum(["initially-immediate", "initially-deferred"]).optional()
1018
+ }).strict();
983
1019
  var canonicalTableSchema = zod.z.object({
984
1020
  table: identifier,
985
1021
  description: zod.z.string().max(2e3).optional(),
@@ -988,7 +1024,7 @@ var canonicalTableSchema = zod.z.object({
988
1024
  /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
989
1025
  source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
990
1026
  columns: zod.z.array(canonicalColumnSchema).default([]),
991
- primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
1027
+ primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
992
1028
  links: zod.z.array(canonicalLinkSchema).default([]),
993
1029
  indexes: zod.z.array(canonicalIndexSchema).default([]),
994
1030
  policies: canonicalPoliciesSchema.default({}),
@@ -999,14 +1035,16 @@ var canonicalTableSchema = zod.z.object({
999
1035
  */
1000
1036
  actions: zod.z.array(canonicalActionSchema).default([]),
1001
1037
  /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
1002
- cowMutations: zod.z.array(canonicalCowMutationSchema).optional()
1038
+ cowMutations: zod.z.array(canonicalCowMutationSchema).optional(),
1039
+ /** Omitted by old definitions, preserving their existing content hashes. */
1040
+ foreignKeys: zod.z.array(canonicalForeignKeySchema).max(100).optional()
1003
1041
  });
1004
1042
  var canonicalViewSchema = zod.z.object({
1005
1043
  view: identifier,
1006
1044
  description: zod.z.string().max(2e3).optional(),
1007
1045
  columns: zod.z.array(canonicalColumnSchema).min(1),
1008
1046
  /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
1009
- primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
1047
+ primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
1010
1048
  links: zod.z.array(canonicalLinkSchema).default([]),
1011
1049
  policies: zod.z.object({ read: policyExpressionSchema }).strict(),
1012
1050
  query: zod.z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
@@ -1019,6 +1057,9 @@ var canonicalViewSchema = zod.z.object({
1019
1057
  if (column.default !== void 0 || column.defaultFn !== void 0) {
1020
1058
  ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare a default` });
1021
1059
  }
1060
+ if (column.onUpdateFn !== void 0) {
1061
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare onUpdateFn` });
1062
+ }
1022
1063
  if (column.unique) {
1023
1064
  ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare unique` });
1024
1065
  }
@@ -1052,6 +1093,45 @@ var canonicalDefinitionSchema = zod.z.object({
1052
1093
  ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
1053
1094
  }
1054
1095
  seen.add(table.table);
1096
+ if (table.source !== void 0) {
1097
+ for (const [columnIndex, column] of table.columns.entries()) {
1098
+ if (column.onUpdateFn !== void 0) {
1099
+ ctx.addIssue({
1100
+ code: "custom",
1101
+ path: ["tables", index, "columns", columnIndex],
1102
+ message: `synced table '${table.table}' column '${column.name}' cannot declare onUpdateFn`
1103
+ });
1104
+ }
1105
+ }
1106
+ }
1107
+ }
1108
+ const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
1109
+ for (const [tableIndex, table] of definition.tables.entries()) {
1110
+ const names = /* @__PURE__ */ new Set([
1111
+ ...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
1112
+ ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
1113
+ ...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
1114
+ ]);
1115
+ for (const [index, fk] of (table.foreignKeys ?? []).entries()) {
1116
+ const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index], message });
1117
+ if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
1118
+ names.add(fk.name);
1119
+ const target = fkTables.get(fk.target);
1120
+ if (table.source || !target || target.source) {
1121
+ fail("foreign keys must connect owned tables in the same namespace");
1122
+ continue;
1123
+ }
1124
+ if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
1125
+ if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
1126
+ for (const [position, name] of fk.columns.entries()) {
1127
+ const sourceColumn = table.columns.find((column) => column.columnName === name);
1128
+ const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
1129
+ if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
1130
+ else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
1131
+ }
1132
+ const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index2) => index2.unique).map((index2) => index2.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
1133
+ 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");
1134
+ }
1055
1135
  }
1056
1136
  const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
1057
1137
  for (const [tableIndex, table] of definition.tables.entries()) {
@@ -1174,12 +1254,13 @@ function normalizeCanonical(definition) {
1174
1254
  ...definition,
1175
1255
  enums: sortByName(definition.enums),
1176
1256
  tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t2) => {
1177
- const { cowMutations, ...rest } = t2;
1257
+ const { cowMutations, foreignKeys, ...rest } = t2;
1178
1258
  return {
1179
1259
  ...rest,
1180
1260
  links: sortByName(t2.links),
1181
1261
  indexes: [...t2.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
1182
1262
  actions: sortByName(t2.actions),
1263
+ ...foreignKeys?.length ? { foreignKeys: sortByName(foreignKeys) } : {},
1183
1264
  // Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
1184
1265
  ...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
1185
1266
  };
@@ -1375,6 +1456,22 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1375
1456
  } else if (columnLevelPk.length > 0) {
1376
1457
  candidate.primaryKey = columnLevelPk;
1377
1458
  }
1459
+ if (table.config.foreignKeys?.length) {
1460
+ if (table.source) throw new OrmDefinitionError(`synced table "${table.tableName}" cannot enforce foreign keys`, table.file);
1461
+ candidate.foreignKeys = table.config.foreignKeys.map((fk) => {
1462
+ const target = resolveRelationRef({ ref: fk.target, fail: (message) => {
1463
+ throw new OrmDefinitionError(message, table.file);
1464
+ } });
1465
+ if (!isSeqTable(target) || !relationsByName.has(target.tableName)) throw new OrmDefinitionError("foreign key target must be a table in this namespace", table.file);
1466
+ return {
1467
+ name: fk.name,
1468
+ columns: fk.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: `foreign key "${fk.name}" column` })),
1469
+ target: target.tableName,
1470
+ references: fk.references.map((ref) => resolveColumnRef({ relation: target, ref, role: `foreign key "${fk.name}" reference` })),
1471
+ ...fk.deferrable ? { deferrable: fk.deferrable } : {}
1472
+ };
1473
+ });
1474
+ }
1378
1475
  for (const index of table.config.indexes ?? []) {
1379
1476
  const entry = {
1380
1477
  columns: index.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: "index" }))
@@ -1427,6 +1524,12 @@ function buildCanonicalView({
1427
1524
  if (column.default !== void 0 || column.defaultFn !== void 0) {
1428
1525
  throw new OrmDefinitionError(`view "${view.viewName}": column "${propertyName}" cannot declare a default`, view.file);
1429
1526
  }
1527
+ if (column.onUpdateFn !== void 0) {
1528
+ throw new OrmDefinitionError(
1529
+ `view "${view.viewName}": column "${propertyName}" cannot declare onUpdateFn`,
1530
+ view.file
1531
+ );
1532
+ }
1430
1533
  if (column.unique) {
1431
1534
  throw new OrmDefinitionError(`view "${view.viewName}": column "${propertyName}" cannot declare unique`, view.file);
1432
1535
  }
@@ -1687,6 +1790,10 @@ async function loadNamespaceFromDir(dir, options = {}) {
1687
1790
  fsCache: false,
1688
1791
  ...options.ormModulePath !== void 0 ? {
1689
1792
  alias: {
1793
+ // The public /drizzle barrel re-exports this pinned dependency.
1794
+ // Explicitly map it before the root alias: otherwise jiti appends
1795
+ // "/drizzle" to the overridden module's filename.
1796
+ "@sequenceholdings/orm/drizzle": module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('authoring.cjs', document.baseURI).href))).resolve("drizzle-orm"),
1690
1797
  "@sequenceholdings/orm": options.ormModulePath,
1691
1798
  "@sequenceholdings/orm/authoring": options.ormModulePath
1692
1799
  }