@sequenceholdings/orm 0.1.1 → 0.1.3

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 (81) hide show
  1. package/README.md +12 -2
  2. package/dist/authoring.cjs +1801 -0
  3. package/dist/authoring.cjs.map +1 -0
  4. package/dist/authoring.d.cts +539 -0
  5. package/dist/authoring.d.ts +539 -0
  6. package/dist/authoring.js +1759 -0
  7. package/dist/authoring.js.map +1 -0
  8. package/dist/canonical.cjs +552 -0
  9. package/dist/canonical.cjs.map +1 -0
  10. package/dist/canonical.d.cts +603 -0
  11. package/dist/canonical.d.ts +603 -0
  12. package/dist/canonical.js +532 -0
  13. package/dist/canonical.js.map +1 -0
  14. package/dist/cli.cjs +7843 -1815
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.d.cts +8 -1
  17. package/dist/cli.d.ts +8 -1
  18. package/dist/cli.js +7854 -1828
  19. package/dist/cli.js.map +1 -1
  20. package/dist/client.cjs +120 -0
  21. package/dist/client.cjs.map +1 -1
  22. package/dist/client.d.cts +74 -2
  23. package/dist/client.d.ts +74 -2
  24. package/dist/client.js +116 -1
  25. package/dist/client.js.map +1 -1
  26. package/dist/compiler.cjs +3453 -0
  27. package/dist/compiler.cjs.map +1 -0
  28. package/dist/compiler.d.cts +389 -0
  29. package/dist/compiler.d.ts +389 -0
  30. package/dist/compiler.js +3396 -0
  31. package/dist/compiler.js.map +1 -0
  32. package/dist/drizzle.cjs +14 -0
  33. package/dist/drizzle.cjs.map +1 -0
  34. package/dist/drizzle.d.cts +1 -0
  35. package/dist/drizzle.d.ts +1 -0
  36. package/dist/drizzle.js +3 -0
  37. package/dist/drizzle.js.map +1 -0
  38. package/dist/dynamic-input.cjs +391 -0
  39. package/dist/dynamic-input.cjs.map +1 -0
  40. package/dist/dynamic-input.d.cts +111 -0
  41. package/dist/dynamic-input.d.ts +111 -0
  42. package/dist/dynamic-input.js +380 -0
  43. package/dist/dynamic-input.js.map +1 -0
  44. package/dist/index.cjs +3727 -344
  45. package/dist/index.cjs.map +1 -1
  46. package/dist/index.d.cts +236 -59
  47. package/dist/index.d.ts +236 -59
  48. package/dist/index.js +3757 -482
  49. package/dist/index.js.map +1 -1
  50. package/dist/ir.cjs +642 -0
  51. package/dist/ir.cjs.map +1 -0
  52. package/dist/ir.d.cts +1119 -0
  53. package/dist/ir.d.ts +1119 -0
  54. package/dist/ir.js +623 -0
  55. package/dist/ir.js.map +1 -0
  56. package/dist/naming-CTvWSRTf.d.ts +79 -0
  57. package/dist/naming-DRyinU4u.d.cts +79 -0
  58. package/dist/predicate-v2.cjs +1093 -0
  59. package/dist/predicate-v2.cjs.map +1 -0
  60. package/dist/predicate-v2.d.cts +139 -0
  61. package/dist/predicate-v2.d.ts +139 -0
  62. package/dist/predicate-v2.js +1083 -0
  63. package/dist/predicate-v2.js.map +1 -0
  64. package/dist/sdl.cjs +1122 -0
  65. package/dist/sdl.cjs.map +1 -0
  66. package/dist/sdl.d.cts +94 -0
  67. package/dist/sdl.d.ts +94 -0
  68. package/dist/sdl.js +1098 -0
  69. package/dist/sdl.js.map +1 -0
  70. package/package.json +51 -3
  71. package/src/codegen-gql/apollo-hooks.cjs +98 -0
  72. package/src/codegen-gql/server-fns.cjs +101 -0
  73. package/templates/README.md +13 -0
  74. package/templates/orm-namespace/CLAUDE.md +82 -0
  75. package/templates/orm-namespace/codegen.ts +22 -0
  76. package/templates/orm-namespace/gitignore +5 -0
  77. package/templates/orm-namespace/graphql/example/queries.ts +56 -0
  78. package/templates/orm-namespace/package.json +24 -0
  79. package/templates/orm-namespace/schema/tables.ts +71 -0
  80. package/templates/orm-namespace/sequence.config.ts +32 -0
  81. package/templates/orm-namespace/tsconfig.json +15 -0
@@ -0,0 +1,1801 @@
1
+ 'use strict';
2
+
3
+ var drizzleOrm = require('drizzle-orm');
4
+ var pgCore = require('drizzle-orm/pg-core');
5
+ var crypto = require('crypto');
6
+ var zod = require('zod');
7
+ var jiti = require('jiti');
8
+ var promises = require('fs/promises');
9
+ var module$1 = require('module');
10
+ var path = require('path');
11
+
12
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
13
+ // src/authoring/columns.ts
14
+ var COLUMN_BRAND = "seq-column";
15
+ var ENUM_BRAND = "seq-enum";
16
+ function isEnumRef(value) {
17
+ return typeof value === "object" && value !== null && value.kind === ENUM_BRAND;
18
+ }
19
+ var COLUMN_DEFAULT_FNS = ["gen_random_uuid()", "now()"];
20
+ function deriveColumnName(propertyName) {
21
+ return propertyName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase();
22
+ }
23
+ var ColumnBuilder = class {
24
+ kind = COLUMN_BRAND;
25
+ state;
26
+ constructor(type, columnName) {
27
+ this.state = {
28
+ type,
29
+ columnName,
30
+ primaryKey: false,
31
+ notNull: false,
32
+ unique: false,
33
+ hasDefault: false,
34
+ defaultValue: void 0,
35
+ defaultFn: void 0,
36
+ description: void 0,
37
+ renamedFrom: void 0,
38
+ cast: void 0,
39
+ deprecated: false
40
+ };
41
+ }
42
+ /** Marks this column as (part of) the primary key. Implies NOT NULL. */
43
+ primaryKey() {
44
+ this.state.primaryKey = true;
45
+ return this;
46
+ }
47
+ notNull() {
48
+ this.state.notNull = true;
49
+ return this;
50
+ }
51
+ unique() {
52
+ this.state.unique = true;
53
+ return this;
54
+ }
55
+ /**
56
+ * Column default: a literal, or one of the allowlisted SQL functions
57
+ * (`'gen_random_uuid()'` / `'now()'`), which serialize as `defaultFn`.
58
+ */
59
+ default(value) {
60
+ if (value === "gen_random_uuid()" || value === "now()") {
61
+ this.state.defaultFn = value;
62
+ this.state.hasDefault = false;
63
+ this.state.defaultValue = void 0;
64
+ return this;
65
+ }
66
+ this.state.hasDefault = true;
67
+ this.state.defaultValue = value;
68
+ this.state.defaultFn = void 0;
69
+ return this;
70
+ }
71
+ description(text2) {
72
+ this.state.description = text2;
73
+ return this;
74
+ }
75
+ /** Migration rename hint: the previous Postgres column name. */
76
+ renamedFrom(previousColumnName) {
77
+ this.state.renamedFrom = previousColumnName;
78
+ return this;
79
+ }
80
+ /** Migration cast hint: the `USING` SQL applied when this column changes type. */
81
+ cast(usingSql) {
82
+ this.state.cast = usingSql;
83
+ return this;
84
+ }
85
+ /** Emits `@deprecated` in the generated SDL; the column stays queryable. */
86
+ deprecated() {
87
+ this.state.deprecated = true;
88
+ return this;
89
+ }
90
+ /** Whether `.primaryKey()` was called — collected into the table-level PK. */
91
+ get isPrimaryKey() {
92
+ return this.state.primaryKey;
93
+ }
94
+ /** The enum name when this is an enum column, else undefined. */
95
+ get enumName() {
96
+ return typeof this.state.type === "object" ? this.state.type.enum : void 0;
97
+ }
98
+ /** Postgres column name: explicit override, else snake_case of the property name. */
99
+ columnNameFor(propertyName) {
100
+ return this.state.columnName ?? deriveColumnName(propertyName);
101
+ }
102
+ /** Serialize to a canonical v2 column. Validation happens in serializeNamespace. */
103
+ toCanonicalColumn(propertyName) {
104
+ const column = {
105
+ name: propertyName,
106
+ columnName: this.columnNameFor(propertyName),
107
+ type: typeof this.state.type === "object" ? { enum: this.state.type.enum } : this.state.type,
108
+ nullable: !(this.state.notNull || this.state.primaryKey),
109
+ unique: this.state.unique,
110
+ deprecated: this.state.deprecated
111
+ };
112
+ if (this.state.hasDefault) column.default = this.state.defaultValue ?? null;
113
+ if (this.state.defaultFn !== void 0) column.defaultFn = this.state.defaultFn;
114
+ if (this.state.description !== void 0) column.description = this.state.description;
115
+ if (this.state.renamedFrom !== void 0) column.renamedFrom = this.state.renamedFrom;
116
+ if (this.state.cast !== void 0) column.cast = this.state.cast;
117
+ return column;
118
+ }
119
+ };
120
+ function isColumnBuilder(value) {
121
+ return typeof value === "object" && value !== null && value.kind === COLUMN_BRAND;
122
+ }
123
+ var scalar = (type) => (columnName) => new ColumnBuilder(type, columnName);
124
+ var t = {
125
+ uuid: scalar("uuid"),
126
+ text: scalar("text"),
127
+ integer: scalar("integer"),
128
+ bigint: scalar("bigint"),
129
+ numeric: scalar("numeric"),
130
+ double: scalar("double"),
131
+ boolean: scalar("boolean"),
132
+ timestamptz: scalar("timestamptz"),
133
+ date: scalar("date"),
134
+ jsonb: scalar("jsonb"),
135
+ enum: (ref, columnName) => new ColumnBuilder({ enum: ref.name }, columnName)
136
+ };
137
+
138
+ // src/constants.ts
139
+ var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
140
+ var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
141
+ var MAX_IDENTIFIER_LENGTH = 63;
142
+ var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
143
+ var HASH_DOMAIN = "sequence-orm-definitions\0";
144
+
145
+ // src/errors.ts
146
+ var OrmDefinitionError = class extends Error {
147
+ constructor(message, file) {
148
+ super(file ? `${file}: ${message}` : message);
149
+ this.file = file;
150
+ }
151
+ name = "OrmDefinitionError";
152
+ };
153
+
154
+ // src/authoring/registry.ts
155
+ var CONTEXT_STACK_KEY = Symbol.for("sequence.orm.authoring.context-stack.v2");
156
+ function contextStack() {
157
+ const holder = globalThis;
158
+ const existing = holder[CONTEXT_STACK_KEY];
159
+ if (Array.isArray(existing)) {
160
+ return existing;
161
+ }
162
+ const created = [];
163
+ holder[CONTEXT_STACK_KEY] = created;
164
+ return created;
165
+ }
166
+ function beginAuthoringContext() {
167
+ const context = { currentFile: void 0, tables: [], views: [], enums: [], configs: [] };
168
+ contextStack().push(context);
169
+ return context;
170
+ }
171
+ function endAuthoringContext(context) {
172
+ const stack = contextStack();
173
+ const top = stack[stack.length - 1];
174
+ if (top !== context) {
175
+ throw new Error("endAuthoringContext: the given context is not the active one \u2014 begin/end calls are mismatched");
176
+ }
177
+ stack.pop();
178
+ }
179
+ function activeAuthoringContext() {
180
+ const stack = contextStack();
181
+ return stack[stack.length - 1];
182
+ }
183
+ function register(bucket, value) {
184
+ const context = activeAuthoringContext();
185
+ if (!context) return;
186
+ context[bucket].push({ value, file: context.currentFile });
187
+ }
188
+ function registerTable(value) {
189
+ register("tables", value);
190
+ }
191
+ function registerView(value) {
192
+ register("views", value);
193
+ }
194
+ function registerEnum(value) {
195
+ register("enums", value);
196
+ }
197
+ function registerNamespaceConfig(value) {
198
+ register("configs", value);
199
+ }
200
+
201
+ // src/authoring/table.ts
202
+ var TABLE_BRAND = "seq-table";
203
+ var LINK_BRAND = "seq-link";
204
+ function toOne(target, options) {
205
+ return {
206
+ kind: LINK_BRAND,
207
+ linkKind: "toOne",
208
+ target,
209
+ on: options.on,
210
+ references: options.references,
211
+ enforce: options.enforce ?? false,
212
+ description: options.description
213
+ };
214
+ }
215
+ function toMany(target, options) {
216
+ return {
217
+ kind: LINK_BRAND,
218
+ linkKind: "toMany",
219
+ target,
220
+ on: options.on,
221
+ references: options.references,
222
+ enforce: false,
223
+ description: options.description
224
+ };
225
+ }
226
+ function toPlatformUser(options) {
227
+ return {
228
+ kind: LINK_BRAND,
229
+ linkKind: "toPlatformUser",
230
+ target: void 0,
231
+ on: options.on,
232
+ references: options.references,
233
+ enforce: false,
234
+ description: options.description
235
+ };
236
+ }
237
+ function isSeqLink(value) {
238
+ return typeof value === "object" && value !== null && value.kind === LINK_BRAND;
239
+ }
240
+ function isSeqTable(value) {
241
+ return typeof value === "object" && value !== null && value.kind === TABLE_BRAND;
242
+ }
243
+ function assertIdentifier({ value, what, file }) {
244
+ if (!IDENTIFIER_PATTERN.test(value) || value.length > MAX_IDENTIFIER_LENGTH) {
245
+ throw new OrmDefinitionError(
246
+ `${what} "${value}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
247
+ file
248
+ );
249
+ }
250
+ }
251
+ function defineTable(name, columns, config = {}) {
252
+ const file = activeAuthoringContext()?.currentFile;
253
+ assertIdentifier({ value: name, what: "table name", file });
254
+ const entries = Object.entries(columns);
255
+ if (entries.length === 0) {
256
+ throw new OrmDefinitionError(`table "${name}" declares no columns`, file);
257
+ }
258
+ const seenColumnNames = /* @__PURE__ */ new Map();
259
+ for (const [propertyName, builder] of entries) {
260
+ if (!isColumnBuilder(builder)) {
261
+ throw new OrmDefinitionError(
262
+ `table "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
263
+ file
264
+ );
265
+ }
266
+ const columnName = builder.columnNameFor(propertyName);
267
+ assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file });
268
+ const existing = seenColumnNames.get(columnName);
269
+ if (existing !== void 0) {
270
+ throw new OrmDefinitionError(
271
+ `table "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
272
+ file
273
+ );
274
+ }
275
+ seenColumnNames.set(columnName, propertyName);
276
+ }
277
+ const table = { kind: TABLE_BRAND, tableName: name, columns, config, source: void 0, file };
278
+ registerTable(table);
279
+ return table;
280
+ }
281
+ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
282
+ const file = activeAuthoringContext()?.currentFile;
283
+ assertIdentifier({ value: name, what: "table name", file });
284
+ const isTyped = maybeConfig !== void 0;
285
+ const columns = isTyped ? columnsOrConfig : {};
286
+ const config = isTyped ? maybeConfig : columnsOrConfig;
287
+ if (!SYNCED_SOURCE_PATTERN.test(config.source)) {
288
+ throw new OrmDefinitionError(
289
+ `synced table "${name}": source "${config.source}" must match ${SYNCED_SOURCE_PATTERN} (gold layer only)`,
290
+ file
291
+ );
292
+ }
293
+ const entries = Object.entries(columns);
294
+ const seenColumnNames = /* @__PURE__ */ new Map();
295
+ const columnLevelPk = [];
296
+ for (const [propertyName, builder] of entries) {
297
+ if (!isColumnBuilder(builder)) {
298
+ throw new OrmDefinitionError(
299
+ `synced table "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
300
+ file
301
+ );
302
+ }
303
+ const columnName = builder.columnNameFor(propertyName);
304
+ assertIdentifier({
305
+ value: columnName,
306
+ what: `synced table "${name}": column "${propertyName}" column name`,
307
+ file
308
+ });
309
+ const existing = seenColumnNames.get(columnName);
310
+ if (existing !== void 0) {
311
+ throw new OrmDefinitionError(
312
+ `synced table "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
313
+ file
314
+ );
315
+ }
316
+ seenColumnNames.set(columnName, propertyName);
317
+ if (builder.isPrimaryKey) columnLevelPk.push(propertyName);
318
+ }
319
+ if (entries.length > 0) {
320
+ const hasConfigPk = config.primaryKey !== void 0 && config.primaryKey.length > 0;
321
+ if (columnLevelPk.length === 0 && !hasConfigPk) {
322
+ throw new OrmDefinitionError(
323
+ `synced table "${name}" declares columns but no primary key \u2014 use .primaryKey() or config.primaryKey`,
324
+ file
325
+ );
326
+ }
327
+ if (columnLevelPk.length > 0 && hasConfigPk) {
328
+ throw new OrmDefinitionError(
329
+ `synced table "${name}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
330
+ file
331
+ );
332
+ }
333
+ }
334
+ const tableConfig = {};
335
+ if (config.description !== void 0) tableConfig.description = config.description;
336
+ if (config.primaryKey !== void 0) tableConfig.primaryKey = config.primaryKey;
337
+ if (config.links !== void 0) tableConfig.links = config.links;
338
+ if (config.policies !== void 0) tableConfig.policies = { read: config.policies.read };
339
+ const table = {
340
+ kind: TABLE_BRAND,
341
+ tableName: name,
342
+ columns,
343
+ config: tableConfig,
344
+ source: config.source,
345
+ file
346
+ };
347
+ registerTable(table);
348
+ return table;
349
+ }
350
+ function defineEnum(name, values) {
351
+ const file = activeAuthoringContext()?.currentFile;
352
+ assertIdentifier({ value: name, what: "enum name", file });
353
+ if (values.length === 0 || values.length > 100) {
354
+ throw new OrmDefinitionError(`enum "${name}" must declare between 1 and 100 values`, file);
355
+ }
356
+ const seen = /* @__PURE__ */ new Set();
357
+ for (const value of values) {
358
+ if (value.length === 0 || value.length > MAX_IDENTIFIER_LENGTH) {
359
+ throw new OrmDefinitionError(
360
+ `enum "${name}": value ${JSON.stringify(value)} must be 1-${MAX_IDENTIFIER_LENGTH} characters`,
361
+ file
362
+ );
363
+ }
364
+ if (seen.has(value)) {
365
+ throw new OrmDefinitionError(`enum "${name}" declares duplicate value "${value}"`, file);
366
+ }
367
+ seen.add(value);
368
+ }
369
+ const ref = { kind: ENUM_BRAND, name, values: [...values] };
370
+ registerEnum(ref);
371
+ return ref;
372
+ }
373
+ var VIEW_BRAND = "seq-view";
374
+ function isSeqView(value) {
375
+ return typeof value === "object" && value !== null && value.kind === VIEW_BRAND;
376
+ }
377
+ function defineView(name, columns, config) {
378
+ const file = activeAuthoringContext()?.currentFile;
379
+ if (!IDENTIFIER_PATTERN.test(name) || name.length > MAX_IDENTIFIER_LENGTH) {
380
+ throw new OrmDefinitionError(
381
+ `view name "${name}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
382
+ file
383
+ );
384
+ }
385
+ const entries = Object.entries(columns);
386
+ if (entries.length === 0) throw new OrmDefinitionError(`view "${name}" declares no columns`, file);
387
+ const seenColumnNames = /* @__PURE__ */ new Map();
388
+ for (const [propertyName, builder] of entries) {
389
+ if (!isColumnBuilder(builder)) {
390
+ throw new OrmDefinitionError(
391
+ `view "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories`,
392
+ file
393
+ );
394
+ }
395
+ const columnName = builder.columnNameFor(propertyName);
396
+ if (!IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
397
+ throw new OrmDefinitionError(`view "${name}": column "${propertyName}" has invalid column name "${columnName}"`, file);
398
+ }
399
+ const existing = seenColumnNames.get(columnName);
400
+ if (existing !== void 0) {
401
+ throw new OrmDefinitionError(
402
+ `view "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
403
+ file
404
+ );
405
+ }
406
+ seenColumnNames.set(columnName, propertyName);
407
+ }
408
+ const view = { kind: VIEW_BRAND, viewName: name, columns, config, file };
409
+ registerView(view);
410
+ return view;
411
+ }
412
+ var QUERY_COLUMN_FACTORIES = {
413
+ uuid: (name) => pgCore.uuid(name),
414
+ text: (name) => pgCore.text(name),
415
+ integer: (name) => pgCore.integer(name),
416
+ bigint: (name) => pgCore.bigint(name, { mode: "bigint" }),
417
+ numeric: (name) => pgCore.numeric(name),
418
+ double: (name) => pgCore.doublePrecision(name),
419
+ boolean: (name) => pgCore.boolean(name),
420
+ timestamptz: (name) => pgCore.timestamp(name, { withTimezone: true }),
421
+ date: (name) => pgCore.date(name),
422
+ jsonb: (name) => pgCore.jsonb(name)
423
+ };
424
+ function compileViewQueries(input) {
425
+ const schemaName = `orm_${input.namespace}`;
426
+ const schema = pgCore.pgSchema(schemaName);
427
+ const enumFactories = /* @__PURE__ */ new Map();
428
+ for (const entry of input.enums) {
429
+ const values = entry.values;
430
+ enumFactories.set(entry.name, schema.enum(entry.name, values));
431
+ }
432
+ const handles = /* @__PURE__ */ new Map();
433
+ const physicalToLogical = /* @__PURE__ */ new Map();
434
+ const tableByName = new Map(input.tables.map((table) => [table.table, table]));
435
+ for (const authored of input.authoredTables) {
436
+ const canonical = tableByName.get(authored.tableName);
437
+ if (!canonical) continue;
438
+ const physicalSchema = schemaName;
439
+ const physicalName = canonical.table;
440
+ const relationSchema = pgCore.pgSchema(physicalSchema);
441
+ const columns = buildQueryColumns(canonical.columns, enumFactories);
442
+ const handle = relationSchema.table(physicalName, columns);
443
+ handles.set(authored, handle);
444
+ physicalToLogical.set(`${physicalSchema}.${physicalName}`, canonical.table);
445
+ }
446
+ const viewByName = new Map(input.views.map((view) => [view.view, view]));
447
+ for (const authored of input.authoredViews) {
448
+ const canonical = viewByName.get(authored.viewName);
449
+ if (!canonical) continue;
450
+ const columns = buildQueryColumns(canonical.columns, enumFactories);
451
+ const handle = schema.view(canonical.view, columns).existing();
452
+ handles.set(authored, handle);
453
+ physicalToLogical.set(`${schemaName}.${canonical.view}`, canonical.view);
454
+ }
455
+ const dialect = new pgCore.PgDialect();
456
+ const compiled = /* @__PURE__ */ new Map();
457
+ for (const authored of input.authoredViews) {
458
+ const expectedFields = Object.keys(authored.columns);
459
+ const declaredDependencies = /* @__PURE__ */ new Set();
460
+ const relation = (ref) => {
461
+ let resolved;
462
+ try {
463
+ resolved = typeof ref === "function" ? ref() : ref;
464
+ } catch (error) {
465
+ throw new OrmDefinitionError(
466
+ `view "${authored.viewName}" relation thunk threw: ${error instanceof Error ? error.message : String(error)}`,
467
+ authored.file
468
+ );
469
+ }
470
+ if (!isSeqTable(resolved) && !isSeqView(resolved)) {
471
+ throw new OrmDefinitionError(
472
+ `view "${authored.viewName}" relation() expects a table or view defined by this ORM`,
473
+ authored.file
474
+ );
475
+ }
476
+ const handle = handles.get(resolved);
477
+ if (!handle) {
478
+ const name = isSeqTable(resolved) ? resolved.tableName : resolved.viewName;
479
+ throw new OrmDefinitionError(
480
+ `view "${authored.viewName}" references relation "${name}" which is not part of this namespace`,
481
+ authored.file
482
+ );
483
+ }
484
+ declaredDependencies.add(isSeqTable(resolved) ? resolved.tableName : resolved.viewName);
485
+ return handle;
486
+ };
487
+ let queryBuilder;
488
+ try {
489
+ queryBuilder = authored.config.query({ qb: new pgCore.QueryBuilder(), relation });
490
+ } catch (error) {
491
+ if (error instanceof OrmDefinitionError) throw error;
492
+ throw new OrmDefinitionError(
493
+ `view "${authored.viewName}" query callback failed: ${error instanceof Error ? error.message : String(error)}`,
494
+ authored.file
495
+ );
496
+ }
497
+ let config;
498
+ try {
499
+ config = pgCore.getViewConfig(schema.view(authored.viewName).as(queryBuilder));
500
+ } catch (error) {
501
+ throw new OrmDefinitionError(
502
+ `view "${authored.viewName}" query must return a Drizzle select builder: ${error instanceof Error ? error.message : String(error)}`,
503
+ authored.file
504
+ );
505
+ }
506
+ const selectedFields = Object.keys(config.selectedFields);
507
+ if (selectedFields.length !== expectedFields.length || selectedFields.some((field, index) => field !== expectedFields[index])) {
508
+ throw new OrmDefinitionError(
509
+ `view "${authored.viewName}" query selects [${selectedFields.join(", ")}], but declared columns are [${expectedFields.join(", ")}]`,
510
+ authored.file
511
+ );
512
+ }
513
+ if (!config.query) throw new OrmDefinitionError(`view "${authored.viewName}" produced no query`, authored.file);
514
+ const tableDependencies = pgCore.extractUsedTable(config.query).map((physical) => {
515
+ const logical = physicalToLogical.get(physical);
516
+ if (!logical) {
517
+ throw new OrmDefinitionError(
518
+ `view "${authored.viewName}" query references unmanaged relation "${physical}" \u2014 use relation(...) with a namespace table or view`,
519
+ authored.file
520
+ );
521
+ }
522
+ return logical;
523
+ });
524
+ const renderedQuery = dialect.sqlToQuery(config.query.inlineParams());
525
+ if (renderedQuery.params.length > 0) {
526
+ throw new OrmDefinitionError(
527
+ `view "${authored.viewName}" query produced bind parameters which cannot be persisted in view DDL`,
528
+ authored.file
529
+ );
530
+ }
531
+ compiled.set(authored.viewName, {
532
+ query: renderedQuery.sql,
533
+ dependencies: [.../* @__PURE__ */ new Set([...declaredDependencies, ...tableDependencies])].sort()
534
+ });
535
+ }
536
+ return compiled;
537
+ }
538
+ function buildQueryColumns(columns, enumFactories) {
539
+ return Object.fromEntries(columns.map((column) => [column.name, buildQueryColumn(column, enumFactories)]));
540
+ }
541
+ function buildQueryColumn(column, enumFactories) {
542
+ const factory = typeof column.type === "string" ? QUERY_COLUMN_FACTORIES[column.type] : enumFactories.get(column.type.enum);
543
+ if (!factory) {
544
+ const type = typeof column.type === "string" ? column.type : column.type.enum;
545
+ throw new OrmDefinitionError(`view query column '${column.columnName}' references unknown type '${type}'`);
546
+ }
547
+ let builder = factory(column.columnName);
548
+ if (!column.nullable) builder = builder.notNull();
549
+ return builder;
550
+ }
551
+ function relationName(relation) {
552
+ return isSeqTable(relation) ? relation.tableName : relation.viewName;
553
+ }
554
+ function relationKind(relation) {
555
+ return isSeqTable(relation) ? "table" : "view";
556
+ }
557
+ function declaredPrimaryKeyProperties(table) {
558
+ const fromColumns = Object.entries(table.columns).filter(([, builder]) => builder.isPrimaryKey).map(([propertyName]) => propertyName);
559
+ if (fromColumns.length > 0) return fromColumns;
560
+ const configPk = table.config.primaryKey ?? [];
561
+ const resolved = [];
562
+ for (const ref of configPk) {
563
+ if (ref in table.columns) {
564
+ resolved.push(ref);
565
+ continue;
566
+ }
567
+ const byColumnName = Object.entries(table.columns).find(
568
+ ([propertyName, builder]) => builder.columnNameFor(propertyName) === ref
569
+ );
570
+ if (byColumnName) resolved.push(byColumnName[0]);
571
+ }
572
+ return resolved;
573
+ }
574
+ function assertOverlayWinsPrimaryKey({
575
+ table,
576
+ pk,
577
+ role
578
+ }) {
579
+ if (!(pk in table.columns)) {
580
+ throw new OrmDefinitionError(
581
+ `overlayWinsUnion: pk "${pk}" must exist as a property on both overlay and synced tables`
582
+ );
583
+ }
584
+ const declared = declaredPrimaryKeyProperties(table);
585
+ if (declared.length !== 1 || declared[0] !== pk) {
586
+ throw new OrmDefinitionError(
587
+ `overlayWinsUnion: ${role} primary key must be exactly "${pk}" (declared: ${declared.length > 0 ? declared.join(", ") : "none"})`
588
+ );
589
+ }
590
+ }
591
+ function formatCanonicalColumnType(type) {
592
+ return typeof type === "string" ? type : `enum(${type.enum})`;
593
+ }
594
+ function assertOverlayWinsMatchingColumnTypes({
595
+ overlay,
596
+ synced,
597
+ columns
598
+ }) {
599
+ for (const column of columns) {
600
+ const overlayType = overlay.columns[column].toCanonicalColumn(column).type;
601
+ const syncedType = synced.columns[column].toCanonicalColumn(column).type;
602
+ if (JSON.stringify(overlayType) !== JSON.stringify(syncedType)) {
603
+ throw new OrmDefinitionError(
604
+ `overlayWinsUnion: column "${column}" must have the same type on overlay and synced (overlay: ${formatCanonicalColumnType(overlayType)}, synced: ${formatCanonicalColumnType(syncedType)})`
605
+ );
606
+ }
607
+ }
608
+ }
609
+ function overlayWinsUnion(input) {
610
+ const { overlay, synced, pk, tombstone } = input;
611
+ return ({ qb, relation }) => {
612
+ assertOverlayWinsPrimaryKey({ table: overlay, pk, role: "overlay" });
613
+ assertOverlayWinsPrimaryKey({ table: synced, pk, role: "synced" });
614
+ const overlayHandle = relation(overlay);
615
+ const syncedHandle = relation(synced);
616
+ const overlayCols = overlayHandle;
617
+ const syncedCols = syncedHandle;
618
+ const overlayPk = overlayCols[pk];
619
+ const syncedPk = syncedCols[pk];
620
+ if (tombstone !== void 0) {
621
+ const tombstoneBuilder = overlay.columns[tombstone];
622
+ if (!tombstoneBuilder) {
623
+ throw new OrmDefinitionError(
624
+ `overlayWinsUnion: tombstone "${tombstone}" must exist as a property on the overlay table`
625
+ );
626
+ }
627
+ const tombstoneCanonical = tombstoneBuilder.toCanonicalColumn(tombstone);
628
+ if (tombstoneCanonical.type !== "boolean") {
629
+ throw new OrmDefinitionError(
630
+ `overlayWinsUnion: tombstone "${tombstone}" must be a boolean column`
631
+ );
632
+ }
633
+ if (tombstoneCanonical.nullable) {
634
+ throw new OrmDefinitionError(
635
+ `overlayWinsUnion: tombstone "${tombstone}" must be notNull \u2014 nullable tombstones silently drop rows`
636
+ );
637
+ }
638
+ }
639
+ const overlayColumns = Object.keys(overlay.columns);
640
+ const syncedColumns = Object.keys(synced.columns);
641
+ const sharedColumns = overlayColumns.filter((col) => syncedColumns.includes(col) && col !== pk);
642
+ assertOverlayWinsMatchingColumnTypes({
643
+ overlay,
644
+ synced,
645
+ columns: [pk, ...sharedColumns]
646
+ });
647
+ const overlaySelect = {
648
+ [pk]: overlayPk,
649
+ ...Object.fromEntries(sharedColumns.map((col) => [col, overlayCols[col]])),
650
+ provenance: drizzleOrm.sql`'overlay'`.as("provenance")
651
+ };
652
+ const syncedSelect = {
653
+ [pk]: syncedPk,
654
+ ...Object.fromEntries(sharedColumns.map((col) => [col, syncedCols[col]])),
655
+ provenance: drizzleOrm.sql`'synced'`.as("provenance")
656
+ };
657
+ const overlayFrom = qb.select(overlaySelect).from(overlayHandle);
658
+ const overlayQuery = tombstone !== void 0 ? overlayFrom.where(drizzleOrm.eq(overlayCols[tombstone], false)) : overlayFrom;
659
+ const syncedQuery = qb.select(syncedSelect).from(syncedHandle).where(
660
+ drizzleOrm.notExists(
661
+ qb.select({ one: drizzleOrm.sql`1` }).from(overlayHandle).where(drizzleOrm.eq(overlayPk, syncedPk))
662
+ )
663
+ );
664
+ return overlayQuery.unionAll(syncedQuery);
665
+ };
666
+ }
667
+
668
+ // src/authoring/cow-mutations.ts
669
+ function overrideRowMutation(input) {
670
+ const { overlay, synced, pk } = input;
671
+ const name = input.name ?? `override_${overlay.tableName}`;
672
+ assertMutationName(name, "overrideRowMutation");
673
+ assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "overrideRowMutation" });
674
+ const sharedProperties = sharedNonPkProperties({ overlay, synced, pk });
675
+ const setProperties = resolveSetProperties({
676
+ overlay,
677
+ synced,
678
+ pk,
679
+ set: input.set,
680
+ sharedProperties,
681
+ helper: "overrideRowMutation"
682
+ });
683
+ assertMatchingColumns({
684
+ overlay,
685
+ synced,
686
+ properties: [pk, ...sharedProperties],
687
+ helper: "overrideRowMutation"
688
+ });
689
+ return {
690
+ kind: "override",
691
+ name,
692
+ synced: synced.tableName,
693
+ pk,
694
+ copy: [pk, ...sharedProperties],
695
+ set: setProperties
696
+ };
697
+ }
698
+ function revertOverlayMutation(input) {
699
+ const { overlay, synced, pk } = input;
700
+ const name = input.name ?? `revert_${overlay.tableName}`;
701
+ assertMutationName(name, "revertOverlayMutation");
702
+ assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "revertOverlayMutation" });
703
+ assertMatchingColumns({ overlay, synced, properties: [pk], helper: "revertOverlayMutation" });
704
+ return {
705
+ kind: "revert",
706
+ name,
707
+ synced: synced.tableName,
708
+ pk,
709
+ copy: [],
710
+ set: []
711
+ };
712
+ }
713
+ function assertMutationName(name, helper) {
714
+ if (!IDENTIFIER_PATTERN.test(name) || name.length > MAX_IDENTIFIER_LENGTH) {
715
+ throw new OrmDefinitionError(
716
+ `${helper}: mutation name "${name}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`
717
+ );
718
+ }
719
+ }
720
+ function assertOverlayPairPrimaryKey({
721
+ overlay,
722
+ synced,
723
+ pk,
724
+ helper
725
+ }) {
726
+ assertSinglePrimaryKey({ table: overlay, pk, role: "overlay", helper });
727
+ assertSinglePrimaryKey({ table: synced, pk, role: "synced", helper });
728
+ }
729
+ function assertSinglePrimaryKey({
730
+ table,
731
+ pk,
732
+ role,
733
+ helper
734
+ }) {
735
+ if (!(pk in table.columns)) {
736
+ throw new OrmDefinitionError(`${helper}: pk "${pk}" must exist as a property on the ${role} table`);
737
+ }
738
+ const declared = declaredPrimaryKeyProperties2(table);
739
+ if (declared.length !== 1 || declared[0] !== pk) {
740
+ throw new OrmDefinitionError(
741
+ `${helper}: ${role} primary key must be exactly "${pk}" (declared: ${declared.length > 0 ? declared.join(", ") : "none"})`
742
+ );
743
+ }
744
+ }
745
+ function declaredPrimaryKeyProperties2(table) {
746
+ const fromColumns = Object.entries(table.columns).filter(([, builder]) => builder.isPrimaryKey).map(([propertyName]) => propertyName);
747
+ if (fromColumns.length > 0) return fromColumns;
748
+ const configPk = table.config?.primaryKey ?? [];
749
+ const resolved = [];
750
+ for (const ref of configPk) {
751
+ if (ref in table.columns) {
752
+ resolved.push(ref);
753
+ continue;
754
+ }
755
+ const byColumnName = Object.entries(table.columns).find(
756
+ ([propertyName, builder]) => builder.columnNameFor(propertyName) === ref
757
+ );
758
+ if (byColumnName) resolved.push(byColumnName[0]);
759
+ }
760
+ return resolved;
761
+ }
762
+ function sharedNonPkProperties({
763
+ overlay,
764
+ synced,
765
+ pk
766
+ }) {
767
+ return Object.keys(overlay.columns).filter((property) => property !== pk && property in synced.columns);
768
+ }
769
+ function resolveSetProperties({
770
+ overlay,
771
+ synced,
772
+ pk,
773
+ set,
774
+ sharedProperties,
775
+ helper
776
+ }) {
777
+ const properties = set !== void 0 ? [...set] : [...sharedProperties];
778
+ if (properties.length === 0) {
779
+ throw new OrmDefinitionError(
780
+ `${helper}: set must declare at least one non-PK column to update after the copy`
781
+ );
782
+ }
783
+ const seen = /* @__PURE__ */ new Set();
784
+ for (const property of properties) {
785
+ if (property === pk) {
786
+ throw new OrmDefinitionError(`${helper}: set must not include pk "${pk}" \u2014 identity columns are pinned`);
787
+ }
788
+ if (!(property in overlay.columns)) {
789
+ throw new OrmDefinitionError(`${helper}: set column "${property}" must exist on the overlay table`);
790
+ }
791
+ if (!(property in synced.columns)) {
792
+ throw new OrmDefinitionError(
793
+ `${helper}: set column "${property}" must exist on the synced table (contract columns only)`
794
+ );
795
+ }
796
+ if (seen.has(property)) {
797
+ throw new OrmDefinitionError(`${helper}: set column "${property}" is duplicated`);
798
+ }
799
+ seen.add(property);
800
+ }
801
+ return properties;
802
+ }
803
+ function assertMatchingColumns({
804
+ overlay,
805
+ synced,
806
+ properties,
807
+ helper
808
+ }) {
809
+ for (const property of properties) {
810
+ const overlayCanonical = overlay.columns[property].toCanonicalColumn(property);
811
+ const syncedCanonical = synced.columns[property].toCanonicalColumn(property);
812
+ const overlayType = scalarColumnType(overlay, property, helper);
813
+ const syncedType = scalarColumnType(synced, property, helper);
814
+ if (overlayType !== syncedType) {
815
+ throw new OrmDefinitionError(
816
+ `${helper}: column "${property}" must have the same scalar type on overlay and synced (overlay: ${overlayType}, synced: ${syncedType})`
817
+ );
818
+ }
819
+ const overlayColumn = overlay.columns[property].columnNameFor(property);
820
+ const syncedColumn = synced.columns[property].columnNameFor(property);
821
+ if (overlayColumn !== syncedColumn) {
822
+ throw new OrmDefinitionError(
823
+ `${helper}: column "${property}" maps to different Postgres names on overlay (${overlayColumn}) and synced (${syncedColumn})`
824
+ );
825
+ }
826
+ if (syncedCanonical.nullable && !overlayCanonical.nullable) {
827
+ throw new OrmDefinitionError(
828
+ `${helper}: column "${property}" is nullable on synced but NOT NULL on overlay \u2014 a NULL synced value would fail the copy-from-synced insert; make overlay nullable or synced notNull`
829
+ );
830
+ }
831
+ }
832
+ }
833
+ function scalarColumnType(table, property, helper) {
834
+ const type = table.columns[property].toCanonicalColumn(property).type;
835
+ if (typeof type !== "string") {
836
+ throw new OrmDefinitionError(
837
+ `${helper}: column "${property}" on "${table.tableName}" must be a scalar type (enums are not supported in cow mutation arguments)`
838
+ );
839
+ }
840
+ return type;
841
+ }
842
+ var CANONICAL_FORMAT = 2;
843
+ var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
844
+ var identifier = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
845
+ var COLUMN_TYPES = [
846
+ "uuid",
847
+ "text",
848
+ "integer",
849
+ "bigint",
850
+ "numeric",
851
+ "double",
852
+ "boolean",
853
+ "timestamptz",
854
+ "date",
855
+ "jsonb"
856
+ ];
857
+ var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
858
+ var canonicalEnumSchema = zod.z.object({
859
+ name: identifier,
860
+ values: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(100)
861
+ });
862
+ var canonicalColumnSchema = zod.z.object({
863
+ /** Property name in TS (camelCase); columnName is the Postgres name. */
864
+ name: zod.z.string().min(1).max(63),
865
+ columnName: identifier,
866
+ type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier })]),
867
+ nullable: zod.z.boolean().default(true),
868
+ unique: zod.z.boolean().default(false),
869
+ /** Literal default, or one of the allowlisted SQL functions. */
870
+ default: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]).optional(),
871
+ defaultFn: zod.z.enum(["gen_random_uuid()", "now()"]).optional(),
872
+ description: zod.z.string().max(500).optional(),
873
+ /** Migration hints — same semantics as v1. */
874
+ renamedFrom: identifier.optional(),
875
+ cast: zod.z.string().max(200).optional(),
876
+ deprecated: zod.z.boolean().default(false)
877
+ });
878
+ var canonicalLinkSchema = zod.z.object({
879
+ name: zod.z.string().min(1).max(63),
880
+ kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
881
+ /** Target table name (namespace-local). Absent for toPlatformUser. */
882
+ target: identifier.optional(),
883
+ /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
884
+ on: identifier,
885
+ /** Referenced column on the target (default: target's PK / platform user `sub`). */
886
+ references: identifier.optional(),
887
+ /** enforce: true emits a real FK constraint (same-namespace toOne only). */
888
+ enforce: zod.z.boolean().default(false),
889
+ description: zod.z.string().max(500).optional()
890
+ });
891
+ var canonicalIndexSchema = zod.z.object({
892
+ columns: zod.z.array(identifier).min(1),
893
+ unique: zod.z.boolean().default(false),
894
+ name: identifier.optional()
895
+ });
896
+ var policyVariantSchema = zod.z.object({
897
+ /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
898
+ when: zod.z.string().min(1).max(1e3),
899
+ /** Exactly one of: allow-all bypass, or a row predicate. */
900
+ allow: zod.z.literal(true).optional(),
901
+ policy: zod.z.string().min(1).max(2e3).optional()
902
+ }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
903
+ message: "A variant declares exactly one of allow: true or policy"
904
+ });
905
+ var policyExpressionSchema = zod.z.union([
906
+ zod.z.string().min(1).max(2e3),
907
+ zod.z.object({
908
+ default: zod.z.string().min(1).max(2e3),
909
+ variants: zod.z.array(policyVariantSchema).max(10).default([])
910
+ })
911
+ ]);
912
+ var canonicalPoliciesSchema = zod.z.object({
913
+ read: policyExpressionSchema.optional(),
914
+ write: policyExpressionSchema.optional(),
915
+ insert: policyExpressionSchema.optional(),
916
+ update: policyExpressionSchema.optional()
917
+ });
918
+ function isSingleViewQuerySql(value) {
919
+ const query = value.trim();
920
+ if (!/^\(*(?:select|with)\b/i.test(query) || query.includes("\0")) return false;
921
+ let state = "normal";
922
+ for (let index = 0; index < query.length; index += 1) {
923
+ const char = query[index];
924
+ const next = query[index + 1];
925
+ if (state === "single") {
926
+ if (char === "'" && next === "'") index += 1;
927
+ else if (char === "'") state = "normal";
928
+ continue;
929
+ }
930
+ if (state === "double") {
931
+ if (char === '"' && next === '"') index += 1;
932
+ else if (char === '"') state = "normal";
933
+ continue;
934
+ }
935
+ if (char === "'") state = "single";
936
+ else if (char === '"') state = "double";
937
+ else if (char === ";" || char === "-" && next === "-" || char === "/" && next === "*") return false;
938
+ }
939
+ return state === "normal" && !/\$[a-z_0-9]*\$/i.test(query);
940
+ }
941
+ var canonicalActionSchema = zod.z.object({
942
+ name: identifier,
943
+ requires: zod.z.string().max(1e3).optional(),
944
+ params: zod.z.record(zod.z.string(), zod.z.object({
945
+ type: columnTypeSchema,
946
+ nullable: zod.z.boolean().default(false)
947
+ })).default({}),
948
+ executes: zod.z.string().min(1).max(1e4),
949
+ returns: zod.z.enum(["rows", "rowsAffected", "scalar"]).default("rowsAffected"),
950
+ audit: zod.z.object({ action: zod.z.string(), resource: zod.z.string() }).optional()
951
+ });
952
+ var canonicalCowMutationSchema = zod.z.object({
953
+ kind: zod.z.enum(["override", "revert"]),
954
+ name: identifier,
955
+ /** Synced table (same namespace) the overlay copies from / falls back to. */
956
+ synced: identifier,
957
+ /** Single-column primary key PROPERTY (TS camelCase, like column `name`) shared by overlay and synced. */
958
+ pk: zod.z.string().min(1).max(63),
959
+ /** Override: contract properties copied INSERT…SELECT from synced (pk included). Empty for revert. */
960
+ copy: zod.z.array(zod.z.string().min(1).max(63)).default([]),
961
+ /** Override: overlay properties written by the UPDATE (never the pk). Empty for revert. */
962
+ set: zod.z.array(zod.z.string().min(1).max(63)).default([])
963
+ }).superRefine((mutation, ctx) => {
964
+ if (mutation.kind === "override") {
965
+ if (mutation.set.length === 0) {
966
+ ctx.addIssue({ code: "custom", path: ["set"], message: "override mutations must set at least one column" });
967
+ }
968
+ if (!mutation.copy.includes(mutation.pk)) {
969
+ ctx.addIssue({ code: "custom", path: ["copy"], message: "override copy list must include the pk" });
970
+ }
971
+ } else if (mutation.set.length > 0 || mutation.copy.length > 0) {
972
+ ctx.addIssue({ code: "custom", path: ["set"], message: "revert mutations take no copy/set columns" });
973
+ }
974
+ if (mutation.set.includes(mutation.pk)) {
975
+ ctx.addIssue({ code: "custom", path: ["set"], message: "set must not include the pk \u2014 identity columns are pinned" });
976
+ }
977
+ for (const [field, values] of [["copy", mutation.copy], ["set", mutation.set]]) {
978
+ if (new Set(values).size !== values.length) {
979
+ ctx.addIssue({ code: "custom", path: [field], message: `${field} must not contain duplicate columns` });
980
+ }
981
+ }
982
+ });
983
+ var canonicalTableSchema = zod.z.object({
984
+ table: identifier,
985
+ description: zod.z.string().max(2e3).optional(),
986
+ /** Table-level rename hint (new in v2). */
987
+ renamedFrom: identifier.optional(),
988
+ /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
989
+ source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
990
+ columns: zod.z.array(canonicalColumnSchema).default([]),
991
+ primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
992
+ links: zod.z.array(canonicalLinkSchema).default([]),
993
+ indexes: zod.z.array(canonicalIndexSchema).default([]),
994
+ policies: canonicalPoliciesSchema.default({}),
995
+ /**
996
+ * v1-conversion carry-over only. v2 has no action surface — the runtime
997
+ * neither compiles nor executes these; writes are mutations. Kept parseable
998
+ * so stored definitions and their content hashes stay stable.
999
+ */
1000
+ actions: zod.z.array(canonicalActionSchema).default([]),
1001
+ /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
1002
+ cowMutations: zod.z.array(canonicalCowMutationSchema).optional()
1003
+ });
1004
+ var canonicalViewSchema = zod.z.object({
1005
+ view: identifier,
1006
+ description: zod.z.string().max(2e3).optional(),
1007
+ columns: zod.z.array(canonicalColumnSchema).min(1),
1008
+ /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
1009
+ primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
1010
+ links: zod.z.array(canonicalLinkSchema).default([]),
1011
+ policies: zod.z.object({ read: policyExpressionSchema }).strict(),
1012
+ query: zod.z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
1013
+ message: "view query must be one SELECT/WITH statement without comments or dollar-quoted bodies"
1014
+ }),
1015
+ /** Namespace-local logical relation names, sorted for deterministic hashing. */
1016
+ dependencies: zod.z.array(identifier).max(500)
1017
+ }).superRefine((view, ctx) => {
1018
+ for (const [index, column] of view.columns.entries()) {
1019
+ if (column.default !== void 0 || column.defaultFn !== void 0) {
1020
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare a default` });
1021
+ }
1022
+ if (column.unique) {
1023
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare unique` });
1024
+ }
1025
+ if (column.renamedFrom !== void 0 || column.cast !== void 0) {
1026
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare migration hints` });
1027
+ }
1028
+ }
1029
+ for (const [index, link] of view.links.entries()) {
1030
+ if (link.enforce) {
1031
+ ctx.addIssue({ code: "custom", path: ["links", index, "enforce"], message: `view link '${link.name}' cannot enforce a foreign key` });
1032
+ }
1033
+ }
1034
+ });
1035
+ var canonicalDefinitionSchema = zod.z.object({
1036
+ definition_format: zod.z.literal(CANONICAL_FORMAT),
1037
+ namespace: identifier.max(41),
1038
+ description: zod.z.string().max(2e3).optional(),
1039
+ enums: zod.z.array(canonicalEnumSchema).default([]),
1040
+ tables: zod.z.array(canonicalTableSchema).min(0).max(500),
1041
+ /** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
1042
+ views: zod.z.array(canonicalViewSchema).max(500).optional(),
1043
+ /** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
1044
+ capabilities: zod.z.object({
1045
+ roles: zod.z.unknown().optional(),
1046
+ uses: zod.z.array(zod.z.string()).default([])
1047
+ }).default({ uses: [] })
1048
+ }).superRefine((definition, ctx) => {
1049
+ const seen = /* @__PURE__ */ new Set();
1050
+ for (const [index, table] of definition.tables.entries()) {
1051
+ if (seen.has(table.table)) {
1052
+ ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
1053
+ }
1054
+ seen.add(table.table);
1055
+ }
1056
+ const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
1057
+ for (const [tableIndex, table] of definition.tables.entries()) {
1058
+ const cowMutations = table.cowMutations ?? [];
1059
+ if (cowMutations.length === 0) continue;
1060
+ const path = (index, ...rest) => ["tables", tableIndex, "cowMutations", index, ...rest];
1061
+ if (table.source !== void 0) {
1062
+ ctx.addIssue({ code: "custom", path: path(0), message: `synced table '${table.table}' cannot declare cow mutations \u2014 it is read-only` });
1063
+ continue;
1064
+ }
1065
+ for (const [index, mutation] of cowMutations.entries()) {
1066
+ const synced = tableByName.get(mutation.synced);
1067
+ if (!synced || synced.source === void 0) {
1068
+ ctx.addIssue({
1069
+ code: "custom",
1070
+ path: path(index, "synced"),
1071
+ message: `cow mutation '${mutation.name}' references '${mutation.synced}', which is not a synced table in this namespace`
1072
+ });
1073
+ continue;
1074
+ }
1075
+ const overlayColumnsByName = new Map(table.columns.map((column) => [column.name, column]));
1076
+ const syncedColumnsByName = new Map(synced.columns.map((column) => [column.name, column]));
1077
+ let columnsExist = true;
1078
+ for (const property of [mutation.pk, ...mutation.copy, ...mutation.set]) {
1079
+ if (!overlayColumnsByName.has(property)) {
1080
+ columnsExist = false;
1081
+ ctx.addIssue({ code: "custom", path: path(index), message: `cow mutation '${mutation.name}': column '${property}' does not exist on overlay '${table.table}'` });
1082
+ }
1083
+ }
1084
+ for (const property of [mutation.pk, ...mutation.copy]) {
1085
+ if (!syncedColumnsByName.has(property)) {
1086
+ columnsExist = false;
1087
+ ctx.addIssue({ code: "custom", path: path(index), message: `cow mutation '${mutation.name}': column '${property}' does not exist on synced '${mutation.synced}'` });
1088
+ }
1089
+ }
1090
+ if (!columnsExist) continue;
1091
+ const overlayPk = table.primaryKey ?? [];
1092
+ const syncedPk = synced.primaryKey ?? [];
1093
+ if (overlayPk.length !== 1 || overlayPk[0] !== mutation.pk || syncedPk.length !== 1 || syncedPk[0] !== mutation.pk) {
1094
+ ctx.addIssue({
1095
+ code: "custom",
1096
+ path: path(index, "pk"),
1097
+ message: `cow mutation '${mutation.name}': pk '${mutation.pk}' must be the single-column primary key on both '${table.table}' and '${mutation.synced}'`
1098
+ });
1099
+ }
1100
+ for (const property of [mutation.pk, ...mutation.copy]) {
1101
+ const overlayColumn = overlayColumnsByName.get(property);
1102
+ const syncedColumn = syncedColumnsByName.get(property);
1103
+ if (!overlayColumn || !syncedColumn) continue;
1104
+ if (JSON.stringify(overlayColumn.type) !== JSON.stringify(syncedColumn.type)) {
1105
+ ctx.addIssue({
1106
+ code: "custom",
1107
+ path: path(index),
1108
+ message: `cow mutation '${mutation.name}': column '${property}' must have the same type on overlay and synced`
1109
+ });
1110
+ }
1111
+ if (overlayColumn.columnName !== syncedColumn.columnName) {
1112
+ ctx.addIssue({
1113
+ code: "custom",
1114
+ path: path(index),
1115
+ message: `cow mutation '${mutation.name}': column '${property}' maps to different Postgres names on overlay ('${overlayColumn.columnName}') and synced ('${syncedColumn.columnName}')`
1116
+ });
1117
+ }
1118
+ if (syncedColumn.nullable !== false && overlayColumn.nullable === false) {
1119
+ ctx.addIssue({
1120
+ code: "custom",
1121
+ path: path(index),
1122
+ message: `cow mutation '${mutation.name}': column '${property}' is nullable on synced but NOT NULL on overlay \u2014 a NULL synced value would fail the copy`
1123
+ });
1124
+ }
1125
+ }
1126
+ }
1127
+ }
1128
+ for (const [index, view] of (definition.views ?? []).entries()) {
1129
+ if (seen.has(view.view)) {
1130
+ ctx.addIssue({ code: "custom", path: ["views", index, "view"], message: `duplicate relation '${view.view}'` });
1131
+ }
1132
+ seen.add(view.view);
1133
+ }
1134
+ const views = definition.views ?? [];
1135
+ const viewByName = new Map(views.map((view) => [view.view, view]));
1136
+ for (const [viewIndex, view] of views.entries()) {
1137
+ for (const [dependencyIndex, dependency] of view.dependencies.entries()) {
1138
+ if (!seen.has(dependency)) {
1139
+ ctx.addIssue({
1140
+ code: "custom",
1141
+ path: ["views", viewIndex, "dependencies", dependencyIndex],
1142
+ message: `view '${view.view}' depends on unknown relation '${dependency}'`
1143
+ });
1144
+ }
1145
+ }
1146
+ }
1147
+ const visiting = /* @__PURE__ */ new Set();
1148
+ const visited = /* @__PURE__ */ new Set();
1149
+ let cycleReported = false;
1150
+ const visit = (name, path) => {
1151
+ if (visited.has(name) || cycleReported) return;
1152
+ if (visiting.has(name)) {
1153
+ cycleReported = true;
1154
+ ctx.addIssue({
1155
+ code: "custom",
1156
+ path: ["views"],
1157
+ message: `view dependencies form a cycle: ${[...path, name].join(" -> ")}`
1158
+ });
1159
+ return;
1160
+ }
1161
+ visiting.add(name);
1162
+ const view = viewByName.get(name);
1163
+ for (const dependency of view?.dependencies ?? []) {
1164
+ if (viewByName.has(dependency)) visit(dependency, [...path, name]);
1165
+ }
1166
+ visiting.delete(name);
1167
+ visited.add(name);
1168
+ };
1169
+ for (const view of views) visit(view.view, []);
1170
+ });
1171
+ function normalizeCanonical(definition) {
1172
+ const sortByName = (items) => [...items].sort((a, b) => a.name.localeCompare(b.name));
1173
+ const normalized = {
1174
+ ...definition,
1175
+ enums: sortByName(definition.enums),
1176
+ tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t2) => {
1177
+ const { cowMutations, ...rest } = t2;
1178
+ return {
1179
+ ...rest,
1180
+ links: sortByName(t2.links),
1181
+ indexes: [...t2.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
1182
+ actions: sortByName(t2.actions),
1183
+ // Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
1184
+ ...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
1185
+ };
1186
+ })
1187
+ };
1188
+ if (definition.views !== void 0) {
1189
+ normalized.views = [...definition.views].sort((a, b) => a.view.localeCompare(b.view)).map((view) => ({
1190
+ ...view,
1191
+ links: sortByName(view.links),
1192
+ dependencies: [...view.dependencies].sort()
1193
+ }));
1194
+ }
1195
+ return normalized;
1196
+ }
1197
+ function stableJson(value) {
1198
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
1199
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
1200
+ const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([a], [b]) => a < b ? -1 : 1).map(([key, entryValue]) => `${JSON.stringify(key)}:${stableJson(entryValue)}`);
1201
+ return `{${entries.join(",")}}`;
1202
+ }
1203
+
1204
+ // src/authoring/namespace.ts
1205
+ var NAMESPACE_CONFIG_BRAND = "seq-namespace-config";
1206
+ function isSeqNamespaceConfig(value) {
1207
+ return typeof value === "object" && value !== null && value.kind === NAMESPACE_CONFIG_BRAND;
1208
+ }
1209
+ function defineNamespace(input) {
1210
+ const file = activeAuthoringContext()?.currentFile;
1211
+ if (!NAMESPACE_PATTERN.test(input.namespace)) {
1212
+ throw new OrmDefinitionError(`namespace "${input.namespace}" must match ${NAMESPACE_PATTERN}`, file);
1213
+ }
1214
+ const config = {
1215
+ kind: NAMESPACE_CONFIG_BRAND,
1216
+ namespace: input.namespace,
1217
+ description: input.description,
1218
+ capabilities: input.capabilities ?? {},
1219
+ file
1220
+ };
1221
+ registerNamespaceConfig(config);
1222
+ return config;
1223
+ }
1224
+ function serializeNamespace(input) {
1225
+ const { config, tables } = input;
1226
+ const views = input.views ?? [];
1227
+ const enums = input.enums ?? [];
1228
+ const tablesByName = /* @__PURE__ */ new Map();
1229
+ for (const table of tables) {
1230
+ const existing = tablesByName.get(table.tableName);
1231
+ if (existing) {
1232
+ throw new OrmDefinitionError(
1233
+ `duplicate table "${table.tableName}" (defined in ${describeFile(existing)} and ${describeFile(table)})`,
1234
+ table.file
1235
+ );
1236
+ }
1237
+ tablesByName.set(table.tableName, table);
1238
+ }
1239
+ const viewsByName = /* @__PURE__ */ new Map();
1240
+ for (const view of views) {
1241
+ const existing = viewsByName.get(view.viewName);
1242
+ if (existing) {
1243
+ throw new OrmDefinitionError(
1244
+ `duplicate view "${view.viewName}" (defined in ${describeFile(existing)} and ${describeFile(view)})`,
1245
+ view.file
1246
+ );
1247
+ }
1248
+ viewsByName.set(view.viewName, view);
1249
+ }
1250
+ const relationsByName = /* @__PURE__ */ new Map();
1251
+ for (const relation of [...tables, ...views]) {
1252
+ const name = relationName(relation);
1253
+ const existing = relationsByName.get(name);
1254
+ if (existing) {
1255
+ throw new OrmDefinitionError(
1256
+ `duplicate relation "${name}" (defined as ${relationKind(existing)} in ${describeFile(existing)} and ${relationKind(relation)} in ${describeFile(relation)})`,
1257
+ relation.file
1258
+ );
1259
+ }
1260
+ relationsByName.set(name, relation);
1261
+ }
1262
+ const enumsByName = /* @__PURE__ */ new Map();
1263
+ for (const ref of enums) {
1264
+ if (enumsByName.has(ref.name)) {
1265
+ throw new OrmDefinitionError(`duplicate enum "${ref.name}"`, config.file);
1266
+ }
1267
+ enumsByName.set(ref.name, ref);
1268
+ }
1269
+ const canonicalTables = tables.map((table) => buildCanonicalTable({ table, relationsByName, enumsByName }));
1270
+ const canonicalEnums = [...enumsByName.values()].map((ref) => ({
1271
+ name: ref.name,
1272
+ values: [...ref.values]
1273
+ }));
1274
+ const viewDrafts = views.map((view) => buildCanonicalView({ view, relationsByName, enumsByName }));
1275
+ const compiledViews = compileViewQueries({
1276
+ namespace: config.namespace,
1277
+ tables: canonicalTables,
1278
+ views: viewDrafts,
1279
+ enums: canonicalEnums,
1280
+ authoredTables: tables,
1281
+ authoredViews: views
1282
+ });
1283
+ const canonicalViews = viewDrafts.map((draft) => {
1284
+ const compiled = compiledViews.get(draft.view);
1285
+ if (!compiled) throw new OrmDefinitionError(`view "${draft.view}" did not compile`, viewsByName.get(draft.view)?.file);
1286
+ return canonicalViewSchema.parse({ ...draft, ...compiled });
1287
+ });
1288
+ assertAcyclicViewDependencies(canonicalViews);
1289
+ const candidate = {
1290
+ definition_format: CANONICAL_FORMAT,
1291
+ namespace: config.namespace,
1292
+ enums: canonicalEnums,
1293
+ tables: canonicalTables,
1294
+ capabilities: {
1295
+ ...config.capabilities.roles !== void 0 ? { roles: config.capabilities.roles } : {},
1296
+ uses: [...config.capabilities.uses ?? []]
1297
+ }
1298
+ };
1299
+ if (config.description !== void 0) candidate.description = config.description;
1300
+ if (canonicalViews.length > 0) candidate.views = canonicalViews;
1301
+ let parsed;
1302
+ try {
1303
+ parsed = canonicalDefinitionSchema.parse(candidate);
1304
+ } catch (error) {
1305
+ if (error instanceof zod.z.ZodError) {
1306
+ throw new OrmDefinitionError(`namespace "${config.namespace}": ${formatZodIssues(error)}`, config.file);
1307
+ }
1308
+ throw error;
1309
+ }
1310
+ return normalizeCanonical(parsed);
1311
+ }
1312
+ function computeCanonicalHash(definition) {
1313
+ return crypto.createHash("sha256").update(HASH_DOMAIN).update(stableJson(definition)).digest("hex");
1314
+ }
1315
+ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1316
+ const candidate = {
1317
+ table: table.tableName,
1318
+ columns: [],
1319
+ links: [],
1320
+ indexes: [],
1321
+ policies: {},
1322
+ actions: []
1323
+ };
1324
+ if (table.config.description !== void 0) candidate.description = table.config.description;
1325
+ if (table.config.renamedFrom !== void 0) candidate.renamedFrom = table.config.renamedFrom;
1326
+ if (table.source !== void 0) candidate.source = table.source;
1327
+ if (table.source !== void 0) {
1328
+ if (table.config.indexes !== void 0 && table.config.indexes.length > 0) {
1329
+ throw new OrmDefinitionError(
1330
+ `synced table "${table.tableName}" is system-owned; indexes are not allowed`,
1331
+ table.file
1332
+ );
1333
+ }
1334
+ if (table.config.cowMutations !== void 0 && table.config.cowMutations.length > 0) {
1335
+ throw new OrmDefinitionError(
1336
+ `synced table "${table.tableName}" is read-only; cow mutations belong on the overlay table`,
1337
+ table.file
1338
+ );
1339
+ }
1340
+ if (table.config.policies?.write !== void 0 || table.config.policies?.insert !== void 0 || table.config.policies?.update !== void 0) {
1341
+ throw new OrmDefinitionError(`synced table "${table.tableName}" is read-only`, table.file);
1342
+ }
1343
+ for (const [linkName, link] of Object.entries(table.config.links ?? {})) {
1344
+ if (link.enforce) {
1345
+ throw new OrmDefinitionError(
1346
+ `synced table "${table.tableName}": link "${linkName}" cannot enforce foreign keys \u2014 its DDL is owned by the sync`,
1347
+ table.file
1348
+ );
1349
+ }
1350
+ }
1351
+ }
1352
+ const columnLevelPk = [];
1353
+ for (const [propertyName, builder] of Object.entries(table.columns)) {
1354
+ const column = builder.toCanonicalColumn(propertyName);
1355
+ const enumName = builder.enumName;
1356
+ if (enumName !== void 0 && !enumsByName.has(enumName)) {
1357
+ throw new OrmDefinitionError(
1358
+ `table "${table.tableName}": column "${propertyName}" references enum "${enumName}" which is not passed to serializeNamespace \u2014 declare it with defineEnum and include it`,
1359
+ table.file
1360
+ );
1361
+ }
1362
+ if (builder.isPrimaryKey) columnLevelPk.push(column.columnName);
1363
+ candidate.columns?.push(column);
1364
+ }
1365
+ if (table.config.primaryKey !== void 0) {
1366
+ if (columnLevelPk.length > 0) {
1367
+ throw new OrmDefinitionError(
1368
+ `table "${table.tableName}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
1369
+ table.file
1370
+ );
1371
+ }
1372
+ candidate.primaryKey = table.config.primaryKey.map(
1373
+ (ref) => resolveColumnRef({ relation: table, ref, role: "primaryKey" })
1374
+ );
1375
+ } else if (columnLevelPk.length > 0) {
1376
+ candidate.primaryKey = columnLevelPk;
1377
+ }
1378
+ for (const index of table.config.indexes ?? []) {
1379
+ const entry = {
1380
+ columns: index.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: "index" }))
1381
+ };
1382
+ if (index.unique !== void 0) entry.unique = index.unique;
1383
+ if (index.name !== void 0) entry.name = index.name;
1384
+ candidate.indexes?.push(entry);
1385
+ }
1386
+ for (const [linkName, link] of Object.entries(table.config.links ?? {})) {
1387
+ candidate.links?.push(buildCanonicalLink({ relation: table, linkName, link, relationsByName }));
1388
+ }
1389
+ const policies = {};
1390
+ if (table.config.policies?.read !== void 0) policies.read = toCanonicalPolicy(table.config.policies.read);
1391
+ if (table.config.policies?.write !== void 0) policies.write = toCanonicalPolicy(table.config.policies.write);
1392
+ if (table.config.policies?.insert !== void 0) policies.insert = toCanonicalPolicy(table.config.policies.insert);
1393
+ if (table.config.policies?.update !== void 0) policies.update = toCanonicalPolicy(table.config.policies.update);
1394
+ candidate.policies = policies;
1395
+ const cowMutations = table.config.cowMutations ?? [];
1396
+ if (cowMutations.length > 0) {
1397
+ candidate.cowMutations = cowMutations.map((mutation) => ({
1398
+ kind: mutation.kind,
1399
+ name: mutation.name,
1400
+ synced: mutation.synced,
1401
+ pk: mutation.pk,
1402
+ copy: [...mutation.copy],
1403
+ set: [...mutation.set]
1404
+ }));
1405
+ }
1406
+ try {
1407
+ return canonicalTableSchema.parse(candidate);
1408
+ } catch (error) {
1409
+ if (error instanceof zod.z.ZodError) {
1410
+ throw new OrmDefinitionError(`table "${table.tableName}": ${formatZodIssues(error)}`, table.file);
1411
+ }
1412
+ throw error;
1413
+ }
1414
+ }
1415
+ function buildCanonicalView({
1416
+ view,
1417
+ relationsByName,
1418
+ enumsByName
1419
+ }) {
1420
+ if (view.config.policies?.read === void 0) {
1421
+ throw new OrmDefinitionError(`view "${view.viewName}" must declare policies.read`, view.file);
1422
+ }
1423
+ const columns = [];
1424
+ const columnLevelPk = [];
1425
+ for (const [propertyName, builder] of Object.entries(view.columns)) {
1426
+ const column = builder.toCanonicalColumn(propertyName);
1427
+ if (column.default !== void 0 || column.defaultFn !== void 0) {
1428
+ throw new OrmDefinitionError(`view "${view.viewName}": column "${propertyName}" cannot declare a default`, view.file);
1429
+ }
1430
+ if (column.unique) {
1431
+ throw new OrmDefinitionError(`view "${view.viewName}": column "${propertyName}" cannot declare unique`, view.file);
1432
+ }
1433
+ if (column.renamedFrom !== void 0 || column.cast !== void 0) {
1434
+ throw new OrmDefinitionError(`view "${view.viewName}": column "${propertyName}" cannot declare migration hints`, view.file);
1435
+ }
1436
+ const enumName = builder.enumName;
1437
+ if (enumName !== void 0 && !enumsByName.has(enumName)) {
1438
+ throw new OrmDefinitionError(
1439
+ `view "${view.viewName}": column "${propertyName}" references enum "${enumName}" which is not part of this namespace`,
1440
+ view.file
1441
+ );
1442
+ }
1443
+ if (builder.isPrimaryKey) columnLevelPk.push(column.columnName);
1444
+ columns.push(column);
1445
+ }
1446
+ let primaryKey;
1447
+ if (view.config.primaryKey !== void 0) {
1448
+ if (columnLevelPk.length > 0) {
1449
+ throw new OrmDefinitionError(
1450
+ `view "${view.viewName}": declare the logical primary key either on columns or with config.primaryKey \u2014 not both`,
1451
+ view.file
1452
+ );
1453
+ }
1454
+ primaryKey = view.config.primaryKey.map((ref) => resolveColumnRef({ relation: view, ref, role: "primaryKey" }));
1455
+ } else if (columnLevelPk.length > 0) {
1456
+ primaryKey = columnLevelPk;
1457
+ }
1458
+ const links = Object.entries(view.config.links ?? {}).map(
1459
+ ([linkName, link]) => buildCanonicalLink({ relation: view, linkName, link, relationsByName })
1460
+ );
1461
+ return {
1462
+ view: view.viewName,
1463
+ ...view.config.description !== void 0 ? { description: view.config.description } : {},
1464
+ columns,
1465
+ ...primaryKey !== void 0 ? { primaryKey } : {},
1466
+ links,
1467
+ policies: { read: toCanonicalPolicy(view.config.policies.read) }
1468
+ };
1469
+ }
1470
+ function buildCanonicalLink({
1471
+ relation,
1472
+ linkName,
1473
+ link,
1474
+ relationsByName
1475
+ }) {
1476
+ const ownerKind = relationKind(relation);
1477
+ const ownerName = relationName(relation);
1478
+ const fail = (message) => {
1479
+ throw new OrmDefinitionError(`${ownerKind} "${ownerName}": link "${linkName}" ${message}`, relation.file);
1480
+ };
1481
+ if (link.enforce && link.linkKind !== "toOne") {
1482
+ return fail("sets enforce: true, which is only supported on toOne links");
1483
+ }
1484
+ if (link.linkKind === "toPlatformUser") {
1485
+ if (link.target !== void 0) return fail("is toPlatformUser and must not declare a target table");
1486
+ return {
1487
+ name: linkName,
1488
+ kind: "toPlatformUser",
1489
+ on: resolveColumnRef({ relation, ref: link.on, role: `link "${linkName}" on` }),
1490
+ ...link.references !== void 0 ? { references: deriveColumnName(link.references) } : {},
1491
+ enforce: false,
1492
+ ...link.description !== void 0 ? { description: link.description } : {}
1493
+ };
1494
+ }
1495
+ const target = resolveRelationRef({ ref: link.target, fail });
1496
+ const targetName = relationName(target);
1497
+ if (!relationsByName.has(targetName)) {
1498
+ return fail(`targets ${relationKind(target)} "${targetName}" which is not part of this namespace`);
1499
+ }
1500
+ if (link.enforce && (!isSeqTable(relation) || !isSeqTable(target))) {
1501
+ return fail("cannot enforce a PostgreSQL foreign key involving a view");
1502
+ }
1503
+ const owningSide = link.linkKind === "toOne" ? relation : target;
1504
+ const referencedSide = link.linkKind === "toOne" ? target : relation;
1505
+ return {
1506
+ name: linkName,
1507
+ kind: link.linkKind,
1508
+ target: targetName,
1509
+ on: resolveColumnRef({ relation: owningSide, ref: link.on, role: `link "${linkName}" on` }),
1510
+ ...link.references !== void 0 ? { references: resolveColumnRef({ relation: referencedSide, ref: link.references, role: `link "${linkName}" references` }) } : {},
1511
+ enforce: link.enforce,
1512
+ ...link.description !== void 0 ? { description: link.description } : {}
1513
+ };
1514
+ }
1515
+ function resolveRelationRef({ ref, fail }) {
1516
+ if (ref === void 0) return fail("declares no target relation");
1517
+ if (typeof ref !== "function") return ref;
1518
+ let resolved;
1519
+ try {
1520
+ resolved = ref();
1521
+ } catch (error) {
1522
+ return fail(
1523
+ `target thunk threw (${error instanceof Error ? error.message : String(error)}) \u2014 lazy refs resolve at serialize time; make sure the target module ran`
1524
+ );
1525
+ }
1526
+ if (!isSeqTable(resolved) && !isSeqView(resolved)) {
1527
+ return fail("target thunk did not return a relation defined with defineTable/defineView");
1528
+ }
1529
+ return resolved;
1530
+ }
1531
+ function resolveColumnRef({ relation, ref, role }) {
1532
+ const direct = relation.columns[ref];
1533
+ if (direct) return direct.columnNameFor(ref);
1534
+ for (const [propertyName, builder] of Object.entries(relation.columns)) {
1535
+ if (builder.columnNameFor(propertyName) === ref) return ref;
1536
+ }
1537
+ const known = Object.keys(relation.columns).join(", ");
1538
+ throw new OrmDefinitionError(
1539
+ `${relationKind(relation)} "${relationName(relation)}": ${role} references unknown column "${ref}" (known columns: ${known || "none"})`,
1540
+ relation.file
1541
+ );
1542
+ }
1543
+ function assertAcyclicViewDependencies(views) {
1544
+ const viewNames = new Set(views.map((view) => view.view));
1545
+ const remaining = new Map(
1546
+ views.map((view) => [view.view, new Set(view.dependencies.filter((dependency) => viewNames.has(dependency)))])
1547
+ );
1548
+ while (remaining.size > 0) {
1549
+ const ready = [...remaining.entries()].filter(([, dependencies]) => [...dependencies].every((dependency) => !remaining.has(dependency))).map(([name]) => name);
1550
+ if (ready.length === 0) {
1551
+ throw new OrmDefinitionError(`view dependencies form a cycle among: ${[...remaining.keys()].sort().join(", ")}`);
1552
+ }
1553
+ for (const name of ready) remaining.delete(name);
1554
+ }
1555
+ }
1556
+ function toCanonicalPolicy(policy) {
1557
+ if (typeof policy === "string") return policy;
1558
+ return {
1559
+ default: policy.default,
1560
+ variants: (policy.variants ?? []).map((variant) => ({
1561
+ when: variant.when,
1562
+ ...variant.allow === true ? { allow: true } : {},
1563
+ ...variant.policy !== void 0 ? { policy: variant.policy } : {}
1564
+ }))
1565
+ };
1566
+ }
1567
+ function describeFile(relation) {
1568
+ return relation.file ?? "<unknown file>";
1569
+ }
1570
+ function formatZodIssues(error) {
1571
+ return error.issues.map((issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message).join("; ");
1572
+ }
1573
+ var SYNCED_CONTRACT_HASH_DOMAIN = "sequence-orm-synced-contract\0";
1574
+ var ORM_TO_PIPELINE_TYPE = {
1575
+ uuid: "uuid",
1576
+ text: "string",
1577
+ integer: "int",
1578
+ bigint: "long",
1579
+ numeric: "numeric",
1580
+ double: "double",
1581
+ boolean: "boolean",
1582
+ timestamptz: "timestamp",
1583
+ date: "date",
1584
+ jsonb: "json"
1585
+ };
1586
+ function isColumnType(value) {
1587
+ return typeof value === "string" && value in ORM_TO_PIPELINE_TYPE;
1588
+ }
1589
+ function syncedContractSurface({
1590
+ namespace,
1591
+ table,
1592
+ columns,
1593
+ primary_key
1594
+ }) {
1595
+ return {
1596
+ namespace,
1597
+ table,
1598
+ columns: Object.fromEntries(
1599
+ Object.entries(columns).map(([name, column]) => [
1600
+ name,
1601
+ { type: column.type, nullable: column.nullable, key: column.key }
1602
+ ])
1603
+ ),
1604
+ primary_key: [...primary_key]
1605
+ };
1606
+ }
1607
+ function computeSyncedContractHash(surface) {
1608
+ return crypto.createHash("sha256").update(SYNCED_CONTRACT_HASH_DOMAIN).update(stableJson(surface)).digest("hex");
1609
+ }
1610
+ function extractOrmSyncedTableContract({
1611
+ namespace,
1612
+ table
1613
+ }) {
1614
+ if (!NAMESPACE_PATTERN.test(namespace)) {
1615
+ throw new OrmDefinitionError(
1616
+ `namespace "${namespace}" must match ${NAMESPACE_PATTERN}`
1617
+ );
1618
+ }
1619
+ if (table.source === void 0) {
1620
+ throw new OrmDefinitionError(
1621
+ `table "${table.table}" is not a synced table \u2014 extractOrmSyncedTableContract requires source: gold.*`
1622
+ );
1623
+ }
1624
+ if (table.columns.length === 0) {
1625
+ throw new OrmDefinitionError(
1626
+ `synced table "${table.table}" has no typed columns \u2014 declare a typed defineSyncedTable contract before exporting`
1627
+ );
1628
+ }
1629
+ const primaryKey = table.primaryKey;
1630
+ if (primaryKey === void 0 || primaryKey.length === 0) {
1631
+ throw new OrmDefinitionError(
1632
+ `synced table "${table.table}" has no primary key \u2014 typed synced contracts require a primary key`
1633
+ );
1634
+ }
1635
+ const pkSet = new Set(primaryKey);
1636
+ const columns = {};
1637
+ for (const column of table.columns) {
1638
+ if (!isColumnType(column.type)) {
1639
+ const enumName = typeof column.type === "object" && column.type !== null && "enum" in column.type ? column.type.enum : String(column.type);
1640
+ throw new OrmDefinitionError(
1641
+ `synced table "${table.table}": column "${column.columnName}" has enum/type '${enumName}', which has no pipeline-type mapping \u2014 use an ORM-mappable scalar`
1642
+ );
1643
+ }
1644
+ columns[column.columnName] = {
1645
+ type: ORM_TO_PIPELINE_TYPE[column.type],
1646
+ nullable: column.nullable,
1647
+ key: pkSet.has(column.columnName)
1648
+ };
1649
+ }
1650
+ for (const key of primaryKey) {
1651
+ if (!(key in columns)) {
1652
+ throw new OrmDefinitionError(
1653
+ `synced table "${table.table}": primary key column "${key}" is not declared in columns`
1654
+ );
1655
+ }
1656
+ }
1657
+ const surface = syncedContractSurface({
1658
+ namespace,
1659
+ table: table.table,
1660
+ columns,
1661
+ primary_key: primaryKey
1662
+ });
1663
+ return {
1664
+ namespace,
1665
+ table: table.table,
1666
+ columns,
1667
+ primary_key: [...primaryKey],
1668
+ content_hash: computeSyncedContractHash(surface)
1669
+ };
1670
+ }
1671
+ var NAMESPACE_CONFIG_FILE = "sequence.config.ts";
1672
+ var SCHEMA_DIR = "schema";
1673
+ async function loadNamespaceFromDir(dir, options = {}) {
1674
+ const root = await promises.realpath(dir).catch(() => {
1675
+ throw new OrmDefinitionError(`namespace directory not found: ${dir}`);
1676
+ });
1677
+ const configPath = path.join(root, NAMESPACE_CONFIG_FILE);
1678
+ const configStat = await promises.stat(configPath).catch(() => void 0);
1679
+ if (!configStat?.isFile()) {
1680
+ throw new OrmDefinitionError(`${NAMESPACE_CONFIG_FILE} not found in ${dir}`);
1681
+ }
1682
+ const schemaFiles = await listSchemaFiles(root);
1683
+ const files = [NAMESPACE_CONFIG_FILE, ...schemaFiles];
1684
+ purgeModuleCache(root);
1685
+ const jiti$1 = jiti.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('authoring.cjs', document.baseURI).href)), {
1686
+ interopDefault: true,
1687
+ fsCache: false,
1688
+ ...options.ormModulePath !== void 0 ? { alias: { "@sequenceholdings/orm": options.ormModulePath } } : {}
1689
+ });
1690
+ const context = beginAuthoringContext();
1691
+ try {
1692
+ for (const file of files) {
1693
+ context.currentFile = file;
1694
+ try {
1695
+ await jiti$1.import(path.join(root, file));
1696
+ } catch (error) {
1697
+ if (error instanceof Error && error.name === "OrmDefinitionError") throw error;
1698
+ throw new OrmDefinitionError(
1699
+ `failed to load: ${error instanceof Error ? error.message : String(error)}`,
1700
+ file
1701
+ );
1702
+ }
1703
+ }
1704
+ } finally {
1705
+ context.currentFile = void 0;
1706
+ endAuthoringContext(context);
1707
+ }
1708
+ const configs = [];
1709
+ for (const entry of context.configs) {
1710
+ if (isSeqNamespaceConfig(entry.value)) configs.push({ value: entry.value, file: entry.file });
1711
+ }
1712
+ const configEntry = configs[0];
1713
+ if (!configEntry) {
1714
+ throw new OrmDefinitionError(
1715
+ "no namespace config registered \u2014 sequence.config.ts must call defineNamespace(...) at import time",
1716
+ NAMESPACE_CONFIG_FILE
1717
+ );
1718
+ }
1719
+ if (configs.length > 1) {
1720
+ const where = configs.map((entry) => entry.file ?? "<unknown file>").join(", ");
1721
+ throw new OrmDefinitionError(
1722
+ `defineNamespace called ${configs.length} times (${where}) \u2014 declare exactly one namespace config`
1723
+ );
1724
+ }
1725
+ const tables = [];
1726
+ for (const entry of context.tables) {
1727
+ if (isSeqTable(entry.value)) tables.push(entry.value);
1728
+ }
1729
+ const views = [];
1730
+ for (const entry of context.views) {
1731
+ if (isSeqView(entry.value)) views.push(entry.value);
1732
+ }
1733
+ if (tables.length === 0 && views.length === 0) {
1734
+ throw new OrmDefinitionError(
1735
+ `no relations registered \u2014 ${SCHEMA_DIR}/*.ts modules must call defineTable(...) or defineView(...) at import time`
1736
+ );
1737
+ }
1738
+ const enums = [];
1739
+ for (const entry of context.enums) {
1740
+ if (isEnumRef(entry.value)) enums.push(entry.value);
1741
+ }
1742
+ const canonical = serializeNamespace({ config: configEntry.value, tables, views, enums });
1743
+ return { canonical, hash: computeCanonicalHash(canonical), files };
1744
+ }
1745
+ async function listSchemaFiles(root) {
1746
+ const schemaDir = path.join(root, SCHEMA_DIR);
1747
+ const entries = await promises.readdir(schemaDir, { withFileTypes: true }).catch(() => []);
1748
+ return entries.filter(
1749
+ (entry) => entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts") && !entry.name.startsWith(".")
1750
+ ).map((entry) => path.join(SCHEMA_DIR, entry.name)).sort();
1751
+ }
1752
+ function purgeModuleCache(root) {
1753
+ const require2 = 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)));
1754
+ const prefix = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
1755
+ for (const key of Object.keys(require2.cache)) {
1756
+ if (key.startsWith(prefix)) delete require2.cache[key];
1757
+ }
1758
+ }
1759
+
1760
+ exports.COLUMN_BRAND = COLUMN_BRAND;
1761
+ exports.COLUMN_DEFAULT_FNS = COLUMN_DEFAULT_FNS;
1762
+ exports.ColumnBuilder = ColumnBuilder;
1763
+ exports.ENUM_BRAND = ENUM_BRAND;
1764
+ exports.LINK_BRAND = LINK_BRAND;
1765
+ exports.NAMESPACE_CONFIG_BRAND = NAMESPACE_CONFIG_BRAND;
1766
+ exports.NAMESPACE_CONFIG_FILE = NAMESPACE_CONFIG_FILE;
1767
+ exports.ORM_TO_PIPELINE_TYPE = ORM_TO_PIPELINE_TYPE;
1768
+ exports.SCHEMA_DIR = SCHEMA_DIR;
1769
+ exports.SYNCED_CONTRACT_HASH_DOMAIN = SYNCED_CONTRACT_HASH_DOMAIN;
1770
+ exports.TABLE_BRAND = TABLE_BRAND;
1771
+ exports.VIEW_BRAND = VIEW_BRAND;
1772
+ exports.activeAuthoringContext = activeAuthoringContext;
1773
+ exports.beginAuthoringContext = beginAuthoringContext;
1774
+ exports.computeCanonicalHash = computeCanonicalHash;
1775
+ exports.computeSyncedContractHash = computeSyncedContractHash;
1776
+ exports.defineEnum = defineEnum;
1777
+ exports.defineNamespace = defineNamespace;
1778
+ exports.defineSyncedTable = defineSyncedTable;
1779
+ exports.defineTable = defineTable;
1780
+ exports.defineView = defineView;
1781
+ exports.deriveColumnName = deriveColumnName;
1782
+ exports.endAuthoringContext = endAuthoringContext;
1783
+ exports.extractOrmSyncedTableContract = extractOrmSyncedTableContract;
1784
+ exports.isColumnBuilder = isColumnBuilder;
1785
+ exports.isEnumRef = isEnumRef;
1786
+ exports.isSeqLink = isSeqLink;
1787
+ exports.isSeqNamespaceConfig = isSeqNamespaceConfig;
1788
+ exports.isSeqTable = isSeqTable;
1789
+ exports.isSeqView = isSeqView;
1790
+ exports.loadNamespaceFromDir = loadNamespaceFromDir;
1791
+ exports.overlayWinsUnion = overlayWinsUnion;
1792
+ exports.overrideRowMutation = overrideRowMutation;
1793
+ exports.revertOverlayMutation = revertOverlayMutation;
1794
+ exports.serializeNamespace = serializeNamespace;
1795
+ exports.syncedContractSurface = syncedContractSurface;
1796
+ exports.t = t;
1797
+ exports.toMany = toMany;
1798
+ exports.toOne = toOne;
1799
+ exports.toPlatformUser = toPlatformUser;
1800
+ //# sourceMappingURL=authoring.cjs.map
1801
+ //# sourceMappingURL=authoring.cjs.map