@sequenceholdings/orm 0.1.0 → 0.1.2

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 (63) hide show
  1. package/README.md +1 -1
  2. package/dist/authoring.cjs +794 -0
  3. package/dist/authoring.cjs.map +1 -0
  4. package/dist/authoring.d.cts +274 -0
  5. package/dist/authoring.d.ts +274 -0
  6. package/dist/authoring.js +763 -0
  7. package/dist/authoring.js.map +1 -0
  8. package/dist/canonical.cjs +146 -0
  9. package/dist/canonical.cjs.map +1 -0
  10. package/dist/canonical.d.cts +369 -0
  11. package/dist/canonical.d.ts +369 -0
  12. package/dist/canonical.js +131 -0
  13. package/dist/canonical.js.map +1 -0
  14. package/dist/cli.cjs +6208 -1693
  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 +6262 -1749
  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 +3001 -0
  27. package/dist/compiler.cjs.map +1 -0
  28. package/dist/compiler.d.cts +581 -0
  29. package/dist/compiler.d.ts +581 -0
  30. package/dist/compiler.js +2947 -0
  31. package/dist/compiler.js.map +1 -0
  32. package/dist/index.cjs +1730 -327
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +126 -56
  35. package/dist/index.d.ts +126 -56
  36. package/dist/index.js +1643 -324
  37. package/dist/index.js.map +1 -1
  38. package/dist/ir.cjs +407 -0
  39. package/dist/ir.cjs.map +1 -0
  40. package/dist/ir.d.cts +1179 -0
  41. package/dist/ir.d.ts +1179 -0
  42. package/dist/ir.js +388 -0
  43. package/dist/ir.js.map +1 -0
  44. package/dist/naming-CTvWSRTf.d.ts +79 -0
  45. package/dist/naming-DRyinU4u.d.cts +79 -0
  46. package/dist/sdl.cjs +833 -0
  47. package/dist/sdl.cjs.map +1 -0
  48. package/dist/sdl.d.cts +96 -0
  49. package/dist/sdl.d.ts +96 -0
  50. package/dist/sdl.js +809 -0
  51. package/dist/sdl.js.map +1 -0
  52. package/package.json +36 -3
  53. package/src/codegen-gql/apollo-hooks.cjs +98 -0
  54. package/src/codegen-gql/server-fns.cjs +101 -0
  55. package/templates/README.md +13 -0
  56. package/templates/orm-namespace/CLAUDE.md +81 -0
  57. package/templates/orm-namespace/codegen.ts +22 -0
  58. package/templates/orm-namespace/gitignore +5 -0
  59. package/templates/orm-namespace/graphql/example/queries.ts +56 -0
  60. package/templates/orm-namespace/package.json +24 -0
  61. package/templates/orm-namespace/schema/tables.ts +71 -0
  62. package/templates/orm-namespace/sequence.config.ts +32 -0
  63. package/templates/orm-namespace/tsconfig.json +15 -0
@@ -0,0 +1,794 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var zod = require('zod');
5
+ var jiti = require('jiti');
6
+ var promises = require('fs/promises');
7
+ var module$1 = require('module');
8
+ var path = require('path');
9
+
10
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
11
+ // src/authoring/columns.ts
12
+ var COLUMN_BRAND = "seq-column";
13
+ var ENUM_BRAND = "seq-enum";
14
+ function isEnumRef(value) {
15
+ return typeof value === "object" && value !== null && value.kind === ENUM_BRAND;
16
+ }
17
+ var COLUMN_DEFAULT_FNS = ["gen_random_uuid()", "now()"];
18
+ function deriveColumnName(propertyName) {
19
+ return propertyName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase();
20
+ }
21
+ var ColumnBuilder = class {
22
+ kind = COLUMN_BRAND;
23
+ state;
24
+ constructor(type, columnName) {
25
+ this.state = {
26
+ type,
27
+ columnName,
28
+ primaryKey: false,
29
+ notNull: false,
30
+ unique: false,
31
+ hasDefault: false,
32
+ defaultValue: void 0,
33
+ defaultFn: void 0,
34
+ description: void 0,
35
+ renamedFrom: void 0,
36
+ cast: void 0,
37
+ deprecated: false
38
+ };
39
+ }
40
+ /** Marks this column as (part of) the primary key. Implies NOT NULL. */
41
+ primaryKey() {
42
+ this.state.primaryKey = true;
43
+ return this;
44
+ }
45
+ notNull() {
46
+ this.state.notNull = true;
47
+ return this;
48
+ }
49
+ unique() {
50
+ this.state.unique = true;
51
+ return this;
52
+ }
53
+ /**
54
+ * Column default: a literal, or one of the allowlisted SQL functions
55
+ * (`'gen_random_uuid()'` / `'now()'`), which serialize as `defaultFn`.
56
+ */
57
+ default(value) {
58
+ if (value === "gen_random_uuid()" || value === "now()") {
59
+ this.state.defaultFn = value;
60
+ this.state.hasDefault = false;
61
+ this.state.defaultValue = void 0;
62
+ return this;
63
+ }
64
+ this.state.hasDefault = true;
65
+ this.state.defaultValue = value;
66
+ this.state.defaultFn = void 0;
67
+ return this;
68
+ }
69
+ description(text) {
70
+ this.state.description = text;
71
+ return this;
72
+ }
73
+ /** Migration rename hint: the previous Postgres column name. */
74
+ renamedFrom(previousColumnName) {
75
+ this.state.renamedFrom = previousColumnName;
76
+ return this;
77
+ }
78
+ /** Migration cast hint: the `USING` SQL applied when this column changes type. */
79
+ cast(usingSql) {
80
+ this.state.cast = usingSql;
81
+ return this;
82
+ }
83
+ /** Emits `@deprecated` in the generated SDL; the column stays queryable. */
84
+ deprecated() {
85
+ this.state.deprecated = true;
86
+ return this;
87
+ }
88
+ /** Whether `.primaryKey()` was called — collected into the table-level PK. */
89
+ get isPrimaryKey() {
90
+ return this.state.primaryKey;
91
+ }
92
+ /** The enum name when this is an enum column, else undefined. */
93
+ get enumName() {
94
+ return typeof this.state.type === "object" ? this.state.type.enum : void 0;
95
+ }
96
+ /** Postgres column name: explicit override, else snake_case of the property name. */
97
+ columnNameFor(propertyName) {
98
+ return this.state.columnName ?? deriveColumnName(propertyName);
99
+ }
100
+ /** Serialize to a canonical v2 column. Validation happens in serializeNamespace. */
101
+ toCanonicalColumn(propertyName) {
102
+ const column = {
103
+ name: propertyName,
104
+ columnName: this.columnNameFor(propertyName),
105
+ type: typeof this.state.type === "object" ? { enum: this.state.type.enum } : this.state.type,
106
+ nullable: !(this.state.notNull || this.state.primaryKey),
107
+ unique: this.state.unique,
108
+ deprecated: this.state.deprecated
109
+ };
110
+ if (this.state.hasDefault) column.default = this.state.defaultValue ?? null;
111
+ if (this.state.defaultFn !== void 0) column.defaultFn = this.state.defaultFn;
112
+ if (this.state.description !== void 0) column.description = this.state.description;
113
+ if (this.state.renamedFrom !== void 0) column.renamedFrom = this.state.renamedFrom;
114
+ if (this.state.cast !== void 0) column.cast = this.state.cast;
115
+ return column;
116
+ }
117
+ };
118
+ function isColumnBuilder(value) {
119
+ return typeof value === "object" && value !== null && value.kind === COLUMN_BRAND;
120
+ }
121
+ var scalar = (type) => (columnName) => new ColumnBuilder(type, columnName);
122
+ var t = {
123
+ uuid: scalar("uuid"),
124
+ text: scalar("text"),
125
+ integer: scalar("integer"),
126
+ bigint: scalar("bigint"),
127
+ numeric: scalar("numeric"),
128
+ double: scalar("double"),
129
+ boolean: scalar("boolean"),
130
+ timestamptz: scalar("timestamptz"),
131
+ date: scalar("date"),
132
+ jsonb: scalar("jsonb"),
133
+ enum: (ref, columnName) => new ColumnBuilder({ enum: ref.name }, columnName)
134
+ };
135
+
136
+ // src/constants.ts
137
+ var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
138
+ var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
139
+ var MAX_IDENTIFIER_LENGTH = 63;
140
+ var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
141
+ var HASH_DOMAIN = "sequence-orm-definitions\0";
142
+
143
+ // src/errors.ts
144
+ var OrmDefinitionError = class extends Error {
145
+ constructor(message, file) {
146
+ super(file ? `${file}: ${message}` : message);
147
+ this.file = file;
148
+ }
149
+ name = "OrmDefinitionError";
150
+ };
151
+
152
+ // src/authoring/registry.ts
153
+ var CONTEXT_STACK_KEY = Symbol.for("sequence.orm.authoring.context-stack.v2");
154
+ function contextStack() {
155
+ const holder = globalThis;
156
+ const existing = holder[CONTEXT_STACK_KEY];
157
+ if (Array.isArray(existing)) {
158
+ return existing;
159
+ }
160
+ const created = [];
161
+ holder[CONTEXT_STACK_KEY] = created;
162
+ return created;
163
+ }
164
+ function beginAuthoringContext() {
165
+ const context = { currentFile: void 0, tables: [], enums: [], configs: [] };
166
+ contextStack().push(context);
167
+ return context;
168
+ }
169
+ function endAuthoringContext(context) {
170
+ const stack = contextStack();
171
+ const top = stack[stack.length - 1];
172
+ if (top !== context) {
173
+ throw new Error("endAuthoringContext: the given context is not the active one \u2014 begin/end calls are mismatched");
174
+ }
175
+ stack.pop();
176
+ }
177
+ function activeAuthoringContext() {
178
+ const stack = contextStack();
179
+ return stack[stack.length - 1];
180
+ }
181
+ function register(bucket, value) {
182
+ const context = activeAuthoringContext();
183
+ if (!context) return;
184
+ context[bucket].push({ value, file: context.currentFile });
185
+ }
186
+ function registerTable(value) {
187
+ register("tables", value);
188
+ }
189
+ function registerEnum(value) {
190
+ register("enums", value);
191
+ }
192
+ function registerNamespaceConfig(value) {
193
+ register("configs", value);
194
+ }
195
+
196
+ // src/authoring/table.ts
197
+ var TABLE_BRAND = "seq-table";
198
+ var LINK_BRAND = "seq-link";
199
+ function toOne(target, options) {
200
+ return {
201
+ kind: LINK_BRAND,
202
+ linkKind: "toOne",
203
+ target,
204
+ on: options.on,
205
+ references: options.references,
206
+ enforce: options.enforce ?? false,
207
+ description: options.description
208
+ };
209
+ }
210
+ function toMany(target, options) {
211
+ return {
212
+ kind: LINK_BRAND,
213
+ linkKind: "toMany",
214
+ target,
215
+ on: options.on,
216
+ references: options.references,
217
+ enforce: false,
218
+ description: options.description
219
+ };
220
+ }
221
+ function toPlatformUser(options) {
222
+ return {
223
+ kind: LINK_BRAND,
224
+ linkKind: "toPlatformUser",
225
+ target: void 0,
226
+ on: options.on,
227
+ references: options.references,
228
+ enforce: false,
229
+ description: options.description
230
+ };
231
+ }
232
+ function isSeqLink(value) {
233
+ return typeof value === "object" && value !== null && value.kind === LINK_BRAND;
234
+ }
235
+ function isSeqTable(value) {
236
+ return typeof value === "object" && value !== null && value.kind === TABLE_BRAND;
237
+ }
238
+ function assertIdentifier({ value, what, file }) {
239
+ if (!IDENTIFIER_PATTERN.test(value) || value.length > MAX_IDENTIFIER_LENGTH) {
240
+ throw new OrmDefinitionError(
241
+ `${what} "${value}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
242
+ file
243
+ );
244
+ }
245
+ }
246
+ function defineTable(name, columns, config = {}) {
247
+ const file = activeAuthoringContext()?.currentFile;
248
+ assertIdentifier({ value: name, what: "table name", file });
249
+ const entries = Object.entries(columns);
250
+ if (entries.length === 0) {
251
+ throw new OrmDefinitionError(`table "${name}" declares no columns`, file);
252
+ }
253
+ const seenColumnNames = /* @__PURE__ */ new Map();
254
+ for (const [propertyName, builder] of entries) {
255
+ if (!isColumnBuilder(builder)) {
256
+ throw new OrmDefinitionError(
257
+ `table "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
258
+ file
259
+ );
260
+ }
261
+ const columnName = builder.columnNameFor(propertyName);
262
+ assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file });
263
+ const existing = seenColumnNames.get(columnName);
264
+ if (existing !== void 0) {
265
+ throw new OrmDefinitionError(
266
+ `table "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
267
+ file
268
+ );
269
+ }
270
+ seenColumnNames.set(columnName, propertyName);
271
+ }
272
+ const table = { kind: TABLE_BRAND, tableName: name, columns, config, source: void 0, file };
273
+ registerTable(table);
274
+ return table;
275
+ }
276
+ function defineSyncedTable(name, config) {
277
+ const file = activeAuthoringContext()?.currentFile;
278
+ assertIdentifier({ value: name, what: "table name", file });
279
+ if (!SYNCED_SOURCE_PATTERN.test(config.source)) {
280
+ throw new OrmDefinitionError(
281
+ `synced table "${name}": source "${config.source}" must match ${SYNCED_SOURCE_PATTERN} (gold layer only)`,
282
+ file
283
+ );
284
+ }
285
+ const table = {
286
+ kind: TABLE_BRAND,
287
+ tableName: name,
288
+ columns: {},
289
+ config: config.description !== void 0 ? { description: config.description } : {},
290
+ source: config.source,
291
+ file
292
+ };
293
+ registerTable(table);
294
+ return table;
295
+ }
296
+ function defineEnum(name, values) {
297
+ const file = activeAuthoringContext()?.currentFile;
298
+ assertIdentifier({ value: name, what: "enum name", file });
299
+ if (values.length === 0 || values.length > 100) {
300
+ throw new OrmDefinitionError(`enum "${name}" must declare between 1 and 100 values`, file);
301
+ }
302
+ const seen = /* @__PURE__ */ new Set();
303
+ for (const value of values) {
304
+ if (value.length === 0 || value.length > MAX_IDENTIFIER_LENGTH) {
305
+ throw new OrmDefinitionError(
306
+ `enum "${name}": value ${JSON.stringify(value)} must be 1-${MAX_IDENTIFIER_LENGTH} characters`,
307
+ file
308
+ );
309
+ }
310
+ if (seen.has(value)) {
311
+ throw new OrmDefinitionError(`enum "${name}" declares duplicate value "${value}"`, file);
312
+ }
313
+ seen.add(value);
314
+ }
315
+ const ref = { kind: ENUM_BRAND, name, values: [...values] };
316
+ registerEnum(ref);
317
+ return ref;
318
+ }
319
+ var CANONICAL_FORMAT = 2;
320
+ var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
321
+ var identifier = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
322
+ var COLUMN_TYPES = [
323
+ "uuid",
324
+ "text",
325
+ "integer",
326
+ "bigint",
327
+ "numeric",
328
+ "double",
329
+ "boolean",
330
+ "timestamptz",
331
+ "date",
332
+ "jsonb"
333
+ ];
334
+ var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
335
+ var canonicalEnumSchema = zod.z.object({
336
+ name: identifier,
337
+ values: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(100)
338
+ });
339
+ var canonicalColumnSchema = zod.z.object({
340
+ /** Property name in TS (camelCase); columnName is the Postgres name. */
341
+ name: zod.z.string().min(1).max(63),
342
+ columnName: identifier,
343
+ type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier })]),
344
+ nullable: zod.z.boolean().default(true),
345
+ unique: zod.z.boolean().default(false),
346
+ /** Literal default, or one of the allowlisted SQL functions. */
347
+ default: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]).optional(),
348
+ defaultFn: zod.z.enum(["gen_random_uuid()", "now()"]).optional(),
349
+ description: zod.z.string().max(500).optional(),
350
+ /** Migration hints — same semantics as v1. */
351
+ renamedFrom: identifier.optional(),
352
+ cast: zod.z.string().max(200).optional(),
353
+ deprecated: zod.z.boolean().default(false)
354
+ });
355
+ var canonicalLinkSchema = zod.z.object({
356
+ name: zod.z.string().min(1).max(63),
357
+ kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
358
+ /** Target table name (namespace-local). Absent for toPlatformUser. */
359
+ target: identifier.optional(),
360
+ /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
361
+ on: identifier,
362
+ /** Referenced column on the target (default: target's PK / platform user `sub`). */
363
+ references: identifier.optional(),
364
+ /** enforce: true emits a real FK constraint (same-namespace toOne only). */
365
+ enforce: zod.z.boolean().default(false),
366
+ description: zod.z.string().max(500).optional()
367
+ });
368
+ var canonicalIndexSchema = zod.z.object({
369
+ columns: zod.z.array(identifier).min(1),
370
+ unique: zod.z.boolean().default(false),
371
+ name: identifier.optional()
372
+ });
373
+ var policyVariantSchema = zod.z.object({
374
+ /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
375
+ when: zod.z.string().min(1).max(1e3),
376
+ /** Exactly one of: allow-all bypass, or a row predicate. */
377
+ allow: zod.z.literal(true).optional(),
378
+ policy: zod.z.string().min(1).max(2e3).optional()
379
+ }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
380
+ message: "A variant declares exactly one of allow: true or policy"
381
+ });
382
+ var policyExpressionSchema = zod.z.union([
383
+ zod.z.string().min(1).max(2e3),
384
+ zod.z.object({
385
+ default: zod.z.string().min(1).max(2e3),
386
+ variants: zod.z.array(policyVariantSchema).max(10).default([])
387
+ })
388
+ ]);
389
+ var canonicalPoliciesSchema = zod.z.object({
390
+ read: policyExpressionSchema.optional(),
391
+ write: policyExpressionSchema.optional()
392
+ });
393
+ var canonicalActionSchema = zod.z.object({
394
+ name: identifier,
395
+ requires: zod.z.string().max(1e3).optional(),
396
+ params: zod.z.record(zod.z.string(), zod.z.object({
397
+ type: columnTypeSchema,
398
+ nullable: zod.z.boolean().default(false)
399
+ })).default({}),
400
+ executes: zod.z.string().min(1).max(1e4),
401
+ returns: zod.z.enum(["rows", "rowsAffected", "scalar"]).default("rowsAffected"),
402
+ audit: zod.z.object({ action: zod.z.string(), resource: zod.z.string() }).optional()
403
+ });
404
+ var canonicalTableSchema = zod.z.object({
405
+ table: identifier,
406
+ description: zod.z.string().max(2e3).optional(),
407
+ /** Table-level rename hint (new in v2). */
408
+ renamedFrom: identifier.optional(),
409
+ /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
410
+ source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
411
+ columns: zod.z.array(canonicalColumnSchema).default([]),
412
+ primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
413
+ links: zod.z.array(canonicalLinkSchema).default([]),
414
+ indexes: zod.z.array(canonicalIndexSchema).default([]),
415
+ policies: canonicalPoliciesSchema.default({}),
416
+ actions: zod.z.array(canonicalActionSchema).default([])
417
+ });
418
+ var canonicalDefinitionSchema = zod.z.object({
419
+ definition_format: zod.z.literal(CANONICAL_FORMAT),
420
+ namespace: identifier.max(41),
421
+ description: zod.z.string().max(2e3).optional(),
422
+ enums: zod.z.array(canonicalEnumSchema).default([]),
423
+ tables: zod.z.array(canonicalTableSchema).min(0).max(500),
424
+ /** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
425
+ capabilities: zod.z.object({
426
+ roles: zod.z.unknown().optional(),
427
+ uses: zod.z.array(zod.z.string()).default([])
428
+ }).default({ uses: [] })
429
+ });
430
+ function normalizeCanonical(definition) {
431
+ const sortByName = (items) => [...items].sort((a, b) => a.name.localeCompare(b.name));
432
+ return {
433
+ ...definition,
434
+ enums: sortByName(definition.enums),
435
+ tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t2) => ({
436
+ ...t2,
437
+ links: sortByName(t2.links),
438
+ indexes: [...t2.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
439
+ actions: sortByName(t2.actions)
440
+ }))
441
+ };
442
+ }
443
+ function stableJson(value) {
444
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
445
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
446
+ 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)}`);
447
+ return `{${entries.join(",")}}`;
448
+ }
449
+
450
+ // src/authoring/namespace.ts
451
+ var NAMESPACE_CONFIG_BRAND = "seq-namespace-config";
452
+ function isSeqNamespaceConfig(value) {
453
+ return typeof value === "object" && value !== null && value.kind === NAMESPACE_CONFIG_BRAND;
454
+ }
455
+ function defineNamespace(input) {
456
+ const file = activeAuthoringContext()?.currentFile;
457
+ if (!NAMESPACE_PATTERN.test(input.namespace)) {
458
+ throw new OrmDefinitionError(`namespace "${input.namespace}" must match ${NAMESPACE_PATTERN}`, file);
459
+ }
460
+ const config = {
461
+ kind: NAMESPACE_CONFIG_BRAND,
462
+ namespace: input.namespace,
463
+ description: input.description,
464
+ capabilities: input.capabilities ?? {},
465
+ file
466
+ };
467
+ registerNamespaceConfig(config);
468
+ return config;
469
+ }
470
+ function serializeNamespace(input) {
471
+ const { config, tables } = input;
472
+ const enums = input.enums ?? [];
473
+ const tablesByName = /* @__PURE__ */ new Map();
474
+ for (const table of tables) {
475
+ const existing = tablesByName.get(table.tableName);
476
+ if (existing) {
477
+ throw new OrmDefinitionError(
478
+ `duplicate table "${table.tableName}" (defined in ${describeFile(existing)} and ${describeFile(table)})`,
479
+ table.file
480
+ );
481
+ }
482
+ tablesByName.set(table.tableName, table);
483
+ }
484
+ const enumsByName = /* @__PURE__ */ new Map();
485
+ for (const ref of enums) {
486
+ if (enumsByName.has(ref.name)) {
487
+ throw new OrmDefinitionError(`duplicate enum "${ref.name}"`, config.file);
488
+ }
489
+ enumsByName.set(ref.name, ref);
490
+ }
491
+ const canonicalTables = tables.map((table) => buildCanonicalTable({ table, tablesByName, enumsByName }));
492
+ const canonicalEnums = [...enumsByName.values()].map((ref) => ({
493
+ name: ref.name,
494
+ values: [...ref.values]
495
+ }));
496
+ const candidate = {
497
+ definition_format: CANONICAL_FORMAT,
498
+ namespace: config.namespace,
499
+ enums: canonicalEnums,
500
+ tables: canonicalTables,
501
+ capabilities: {
502
+ ...config.capabilities.roles !== void 0 ? { roles: config.capabilities.roles } : {},
503
+ uses: [...config.capabilities.uses ?? []]
504
+ }
505
+ };
506
+ if (config.description !== void 0) candidate.description = config.description;
507
+ let parsed;
508
+ try {
509
+ parsed = canonicalDefinitionSchema.parse(candidate);
510
+ } catch (error) {
511
+ if (error instanceof zod.z.ZodError) {
512
+ throw new OrmDefinitionError(`namespace "${config.namespace}": ${formatZodIssues(error)}`, config.file);
513
+ }
514
+ throw error;
515
+ }
516
+ return normalizeCanonical(parsed);
517
+ }
518
+ function computeCanonicalHash(definition) {
519
+ return crypto.createHash("sha256").update(HASH_DOMAIN).update(stableJson(definition)).digest("hex");
520
+ }
521
+ function buildCanonicalTable({ table, tablesByName, enumsByName }) {
522
+ const candidate = {
523
+ table: table.tableName,
524
+ columns: [],
525
+ links: [],
526
+ indexes: [],
527
+ policies: {},
528
+ actions: []
529
+ };
530
+ if (table.config.description !== void 0) candidate.description = table.config.description;
531
+ if (table.config.renamedFrom !== void 0) candidate.renamedFrom = table.config.renamedFrom;
532
+ if (table.source !== void 0) candidate.source = table.source;
533
+ const columnLevelPk = [];
534
+ for (const [propertyName, builder] of Object.entries(table.columns)) {
535
+ const column = builder.toCanonicalColumn(propertyName);
536
+ const enumName = builder.enumName;
537
+ if (enumName !== void 0 && !enumsByName.has(enumName)) {
538
+ throw new OrmDefinitionError(
539
+ `table "${table.tableName}": column "${propertyName}" references enum "${enumName}" which is not passed to serializeNamespace \u2014 declare it with defineEnum and include it`,
540
+ table.file
541
+ );
542
+ }
543
+ if (builder.isPrimaryKey) columnLevelPk.push(column.columnName);
544
+ candidate.columns?.push(column);
545
+ }
546
+ if (table.config.primaryKey !== void 0) {
547
+ if (columnLevelPk.length > 0) {
548
+ throw new OrmDefinitionError(
549
+ `table "${table.tableName}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
550
+ table.file
551
+ );
552
+ }
553
+ candidate.primaryKey = table.config.primaryKey.map(
554
+ (ref) => resolveColumnRef({ table, ref, role: "primaryKey" })
555
+ );
556
+ } else if (columnLevelPk.length > 0) {
557
+ candidate.primaryKey = columnLevelPk;
558
+ }
559
+ for (const index of table.config.indexes ?? []) {
560
+ const entry = {
561
+ columns: index.columns.map((ref) => resolveColumnRef({ table, ref, role: "index" }))
562
+ };
563
+ if (index.unique !== void 0) entry.unique = index.unique;
564
+ if (index.name !== void 0) entry.name = index.name;
565
+ candidate.indexes?.push(entry);
566
+ }
567
+ for (const [linkName, link] of Object.entries(table.config.links ?? {})) {
568
+ candidate.links?.push(buildCanonicalLink({ table, linkName, link, tablesByName }));
569
+ }
570
+ const policies = {};
571
+ if (table.config.policies?.read !== void 0) policies.read = toCanonicalPolicy(table.config.policies.read);
572
+ if (table.config.policies?.write !== void 0) policies.write = toCanonicalPolicy(table.config.policies.write);
573
+ candidate.policies = policies;
574
+ for (const action of table.config.actions ?? []) {
575
+ candidate.actions?.push({
576
+ name: action.name,
577
+ ...action.requires !== void 0 ? { requires: action.requires } : {},
578
+ params: Object.fromEntries(
579
+ Object.entries(action.params ?? {}).map(([name, param]) => [
580
+ name,
581
+ { type: param.type, nullable: param.nullable ?? false }
582
+ ])
583
+ ),
584
+ executes: action.executes,
585
+ returns: action.returns ?? "rowsAffected",
586
+ ...action.audit !== void 0 ? { audit: action.audit } : {}
587
+ });
588
+ }
589
+ try {
590
+ return canonicalTableSchema.parse(candidate);
591
+ } catch (error) {
592
+ if (error instanceof zod.z.ZodError) {
593
+ throw new OrmDefinitionError(`table "${table.tableName}": ${formatZodIssues(error)}`, table.file);
594
+ }
595
+ throw error;
596
+ }
597
+ }
598
+ function buildCanonicalLink({
599
+ table,
600
+ linkName,
601
+ link,
602
+ tablesByName
603
+ }) {
604
+ const fail = (message) => {
605
+ throw new OrmDefinitionError(`table "${table.tableName}": link "${linkName}" ${message}`, table.file);
606
+ };
607
+ if (link.enforce && link.linkKind !== "toOne") {
608
+ return fail("sets enforce: true, which is only supported on toOne links");
609
+ }
610
+ if (link.linkKind === "toPlatformUser") {
611
+ if (link.target !== void 0) return fail("is toPlatformUser and must not declare a target table");
612
+ return {
613
+ name: linkName,
614
+ kind: "toPlatformUser",
615
+ on: resolveColumnRef({ table, ref: link.on, role: `link "${linkName}" on` }),
616
+ ...link.references !== void 0 ? { references: deriveColumnName(link.references) } : {},
617
+ enforce: false,
618
+ ...link.description !== void 0 ? { description: link.description } : {}
619
+ };
620
+ }
621
+ const target = resolveTableRef({ ref: link.target, fail });
622
+ if (!tablesByName.has(target.tableName)) {
623
+ return fail(`targets table "${target.tableName}" which is not part of this namespace`);
624
+ }
625
+ const owningSide = link.linkKind === "toOne" ? table : target;
626
+ return {
627
+ name: linkName,
628
+ kind: link.linkKind,
629
+ target: target.tableName,
630
+ on: resolveColumnRef({ table: owningSide, ref: link.on, role: `link "${linkName}" on` }),
631
+ ...link.references !== void 0 ? { references: resolveColumnRef({ table: target, ref: link.references, role: `link "${linkName}" references` }) } : {},
632
+ enforce: link.enforce,
633
+ ...link.description !== void 0 ? { description: link.description } : {}
634
+ };
635
+ }
636
+ function resolveTableRef({ ref, fail }) {
637
+ if (ref === void 0) return fail("declares no target table");
638
+ if (typeof ref !== "function") return ref;
639
+ let resolved;
640
+ try {
641
+ resolved = ref();
642
+ } catch (error) {
643
+ return fail(
644
+ `target thunk threw (${error instanceof Error ? error.message : String(error)}) \u2014 lazy refs resolve at serialize time; make sure the target module ran`
645
+ );
646
+ }
647
+ if (!isSeqTable(resolved)) return fail("target thunk did not return a table defined with defineTable");
648
+ return resolved;
649
+ }
650
+ function resolveColumnRef({ table, ref, role }) {
651
+ const direct = table.columns[ref];
652
+ if (direct) return direct.columnNameFor(ref);
653
+ for (const [propertyName, builder] of Object.entries(table.columns)) {
654
+ if (builder.columnNameFor(propertyName) === ref) return ref;
655
+ }
656
+ const known = Object.keys(table.columns).join(", ");
657
+ throw new OrmDefinitionError(
658
+ `table "${table.tableName}": ${role} references unknown column "${ref}" (known columns: ${known || "none"})`,
659
+ table.file
660
+ );
661
+ }
662
+ function toCanonicalPolicy(policy) {
663
+ if (typeof policy === "string") return policy;
664
+ return {
665
+ default: policy.default,
666
+ variants: (policy.variants ?? []).map((variant) => ({
667
+ when: variant.when,
668
+ ...variant.allow === true ? { allow: true } : {},
669
+ ...variant.policy !== void 0 ? { policy: variant.policy } : {}
670
+ }))
671
+ };
672
+ }
673
+ function describeFile(table) {
674
+ return table.file ?? "<unknown file>";
675
+ }
676
+ function formatZodIssues(error) {
677
+ return error.issues.map((issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message).join("; ");
678
+ }
679
+ var NAMESPACE_CONFIG_FILE = "sequence.config.ts";
680
+ var SCHEMA_DIR = "schema";
681
+ async function loadNamespaceFromDir(dir, options = {}) {
682
+ const root = await promises.realpath(dir).catch(() => {
683
+ throw new OrmDefinitionError(`namespace directory not found: ${dir}`);
684
+ });
685
+ const configPath = path.join(root, NAMESPACE_CONFIG_FILE);
686
+ const configStat = await promises.stat(configPath).catch(() => void 0);
687
+ if (!configStat?.isFile()) {
688
+ throw new OrmDefinitionError(`${NAMESPACE_CONFIG_FILE} not found in ${dir}`);
689
+ }
690
+ const schemaFiles = await listSchemaFiles(root);
691
+ const files = [NAMESPACE_CONFIG_FILE, ...schemaFiles];
692
+ purgeModuleCache(root);
693
+ 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)), {
694
+ interopDefault: true,
695
+ fsCache: false,
696
+ ...options.ormModulePath !== void 0 ? { alias: { "@sequenceholdings/orm": options.ormModulePath } } : {}
697
+ });
698
+ const context = beginAuthoringContext();
699
+ try {
700
+ for (const file of files) {
701
+ context.currentFile = file;
702
+ try {
703
+ await jiti$1.import(path.join(root, file));
704
+ } catch (error) {
705
+ if (error instanceof Error && error.name === "OrmDefinitionError") throw error;
706
+ throw new OrmDefinitionError(
707
+ `failed to load: ${error instanceof Error ? error.message : String(error)}`,
708
+ file
709
+ );
710
+ }
711
+ }
712
+ } finally {
713
+ context.currentFile = void 0;
714
+ endAuthoringContext(context);
715
+ }
716
+ const configs = [];
717
+ for (const entry of context.configs) {
718
+ if (isSeqNamespaceConfig(entry.value)) configs.push({ value: entry.value, file: entry.file });
719
+ }
720
+ const configEntry = configs[0];
721
+ if (!configEntry) {
722
+ throw new OrmDefinitionError(
723
+ "no namespace config registered \u2014 sequence.config.ts must call defineNamespace(...) at import time",
724
+ NAMESPACE_CONFIG_FILE
725
+ );
726
+ }
727
+ if (configs.length > 1) {
728
+ const where = configs.map((entry) => entry.file ?? "<unknown file>").join(", ");
729
+ throw new OrmDefinitionError(
730
+ `defineNamespace called ${configs.length} times (${where}) \u2014 declare exactly one namespace config`
731
+ );
732
+ }
733
+ const tables = [];
734
+ for (const entry of context.tables) {
735
+ if (isSeqTable(entry.value)) tables.push(entry.value);
736
+ }
737
+ if (tables.length === 0) {
738
+ throw new OrmDefinitionError(
739
+ `no tables registered \u2014 ${SCHEMA_DIR}/*.ts modules must call defineTable(...) at import time`
740
+ );
741
+ }
742
+ const enums = [];
743
+ for (const entry of context.enums) {
744
+ if (isEnumRef(entry.value)) enums.push(entry.value);
745
+ }
746
+ const canonical = serializeNamespace({ config: configEntry.value, tables, enums });
747
+ return { canonical, hash: computeCanonicalHash(canonical), files };
748
+ }
749
+ async function listSchemaFiles(root) {
750
+ const schemaDir = path.join(root, SCHEMA_DIR);
751
+ const entries = await promises.readdir(schemaDir, { withFileTypes: true }).catch(() => []);
752
+ return entries.filter(
753
+ (entry) => entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts") && !entry.name.startsWith(".")
754
+ ).map((entry) => path.join(SCHEMA_DIR, entry.name)).sort();
755
+ }
756
+ function purgeModuleCache(root) {
757
+ 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)));
758
+ const prefix = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
759
+ for (const key of Object.keys(require2.cache)) {
760
+ if (key.startsWith(prefix)) delete require2.cache[key];
761
+ }
762
+ }
763
+
764
+ exports.COLUMN_BRAND = COLUMN_BRAND;
765
+ exports.COLUMN_DEFAULT_FNS = COLUMN_DEFAULT_FNS;
766
+ exports.ColumnBuilder = ColumnBuilder;
767
+ exports.ENUM_BRAND = ENUM_BRAND;
768
+ exports.LINK_BRAND = LINK_BRAND;
769
+ exports.NAMESPACE_CONFIG_BRAND = NAMESPACE_CONFIG_BRAND;
770
+ exports.NAMESPACE_CONFIG_FILE = NAMESPACE_CONFIG_FILE;
771
+ exports.SCHEMA_DIR = SCHEMA_DIR;
772
+ exports.TABLE_BRAND = TABLE_BRAND;
773
+ exports.activeAuthoringContext = activeAuthoringContext;
774
+ exports.beginAuthoringContext = beginAuthoringContext;
775
+ exports.computeCanonicalHash = computeCanonicalHash;
776
+ exports.defineEnum = defineEnum;
777
+ exports.defineNamespace = defineNamespace;
778
+ exports.defineSyncedTable = defineSyncedTable;
779
+ exports.defineTable = defineTable;
780
+ exports.deriveColumnName = deriveColumnName;
781
+ exports.endAuthoringContext = endAuthoringContext;
782
+ exports.isColumnBuilder = isColumnBuilder;
783
+ exports.isEnumRef = isEnumRef;
784
+ exports.isSeqLink = isSeqLink;
785
+ exports.isSeqNamespaceConfig = isSeqNamespaceConfig;
786
+ exports.isSeqTable = isSeqTable;
787
+ exports.loadNamespaceFromDir = loadNamespaceFromDir;
788
+ exports.serializeNamespace = serializeNamespace;
789
+ exports.t = t;
790
+ exports.toMany = toMany;
791
+ exports.toOne = toOne;
792
+ exports.toPlatformUser = toPlatformUser;
793
+ //# sourceMappingURL=authoring.cjs.map
794
+ //# sourceMappingURL=authoring.cjs.map