@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
package/dist/sdl.cjs ADDED
@@ -0,0 +1,1122 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+ var crypto = require('crypto');
5
+
6
+ // src/canonical/v2.ts
7
+ var CANONICAL_FORMAT = 2;
8
+ var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
9
+ var identifier = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
10
+ var COLUMN_TYPES = [
11
+ "uuid",
12
+ "text",
13
+ "integer",
14
+ "bigint",
15
+ "numeric",
16
+ "double",
17
+ "boolean",
18
+ "timestamptz",
19
+ "date",
20
+ "jsonb"
21
+ ];
22
+ var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
23
+ var canonicalEnumSchema = zod.z.object({
24
+ name: identifier,
25
+ values: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(100)
26
+ });
27
+ var canonicalColumnSchema = zod.z.object({
28
+ /** Property name in TS (camelCase); columnName is the Postgres name. */
29
+ name: zod.z.string().min(1).max(63),
30
+ columnName: identifier,
31
+ type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier })]),
32
+ nullable: zod.z.boolean().default(true),
33
+ unique: zod.z.boolean().default(false),
34
+ /** Literal default, or one of the allowlisted SQL functions. */
35
+ default: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]).optional(),
36
+ defaultFn: zod.z.enum(["gen_random_uuid()", "now()"]).optional(),
37
+ description: zod.z.string().max(500).optional(),
38
+ /** Migration hints — same semantics as v1. */
39
+ renamedFrom: identifier.optional(),
40
+ cast: zod.z.string().max(200).optional(),
41
+ deprecated: zod.z.boolean().default(false)
42
+ });
43
+ var canonicalLinkSchema = zod.z.object({
44
+ name: zod.z.string().min(1).max(63),
45
+ kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
46
+ /** Target table name (namespace-local). Absent for toPlatformUser. */
47
+ target: identifier.optional(),
48
+ /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
49
+ on: identifier,
50
+ /** Referenced column on the target (default: target's PK / platform user `sub`). */
51
+ references: identifier.optional(),
52
+ /** enforce: true emits a real FK constraint (same-namespace toOne only). */
53
+ enforce: zod.z.boolean().default(false),
54
+ description: zod.z.string().max(500).optional()
55
+ });
56
+ var canonicalIndexSchema = zod.z.object({
57
+ columns: zod.z.array(identifier).min(1),
58
+ unique: zod.z.boolean().default(false),
59
+ name: identifier.optional()
60
+ });
61
+ var policyVariantSchema = zod.z.object({
62
+ /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
63
+ when: zod.z.string().min(1).max(1e3),
64
+ /** Exactly one of: allow-all bypass, or a row predicate. */
65
+ allow: zod.z.literal(true).optional(),
66
+ policy: zod.z.string().min(1).max(2e3).optional()
67
+ }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
68
+ message: "A variant declares exactly one of allow: true or policy"
69
+ });
70
+ var policyExpressionSchema = zod.z.union([
71
+ zod.z.string().min(1).max(2e3),
72
+ zod.z.object({
73
+ default: zod.z.string().min(1).max(2e3),
74
+ variants: zod.z.array(policyVariantSchema).max(10).default([])
75
+ })
76
+ ]);
77
+ var canonicalPoliciesSchema = zod.z.object({
78
+ read: policyExpressionSchema.optional(),
79
+ write: policyExpressionSchema.optional(),
80
+ insert: policyExpressionSchema.optional(),
81
+ update: policyExpressionSchema.optional()
82
+ });
83
+ function isSingleViewQuerySql(value) {
84
+ const query = value.trim();
85
+ if (!/^\(*(?:select|with)\b/i.test(query) || query.includes("\0")) return false;
86
+ let state = "normal";
87
+ for (let index = 0; index < query.length; index += 1) {
88
+ const char = query[index];
89
+ const next = query[index + 1];
90
+ if (state === "single") {
91
+ if (char === "'" && next === "'") index += 1;
92
+ else if (char === "'") state = "normal";
93
+ continue;
94
+ }
95
+ if (state === "double") {
96
+ if (char === '"' && next === '"') index += 1;
97
+ else if (char === '"') state = "normal";
98
+ continue;
99
+ }
100
+ if (char === "'") state = "single";
101
+ else if (char === '"') state = "double";
102
+ else if (char === ";" || char === "-" && next === "-" || char === "/" && next === "*") return false;
103
+ }
104
+ return state === "normal" && !/\$[a-z_0-9]*\$/i.test(query);
105
+ }
106
+ var canonicalActionSchema = zod.z.object({
107
+ name: identifier,
108
+ requires: zod.z.string().max(1e3).optional(),
109
+ params: zod.z.record(zod.z.string(), zod.z.object({
110
+ type: columnTypeSchema,
111
+ nullable: zod.z.boolean().default(false)
112
+ })).default({}),
113
+ executes: zod.z.string().min(1).max(1e4),
114
+ returns: zod.z.enum(["rows", "rowsAffected", "scalar"]).default("rowsAffected"),
115
+ audit: zod.z.object({ action: zod.z.string(), resource: zod.z.string() }).optional()
116
+ });
117
+ var canonicalCowMutationSchema = zod.z.object({
118
+ kind: zod.z.enum(["override", "revert"]),
119
+ name: identifier,
120
+ /** Synced table (same namespace) the overlay copies from / falls back to. */
121
+ synced: identifier,
122
+ /** Single-column primary key PROPERTY (TS camelCase, like column `name`) shared by overlay and synced. */
123
+ pk: zod.z.string().min(1).max(63),
124
+ /** Override: contract properties copied INSERT…SELECT from synced (pk included). Empty for revert. */
125
+ copy: zod.z.array(zod.z.string().min(1).max(63)).default([]),
126
+ /** Override: overlay properties written by the UPDATE (never the pk). Empty for revert. */
127
+ set: zod.z.array(zod.z.string().min(1).max(63)).default([])
128
+ }).superRefine((mutation, ctx) => {
129
+ if (mutation.kind === "override") {
130
+ if (mutation.set.length === 0) {
131
+ ctx.addIssue({ code: "custom", path: ["set"], message: "override mutations must set at least one column" });
132
+ }
133
+ if (!mutation.copy.includes(mutation.pk)) {
134
+ ctx.addIssue({ code: "custom", path: ["copy"], message: "override copy list must include the pk" });
135
+ }
136
+ } else if (mutation.set.length > 0 || mutation.copy.length > 0) {
137
+ ctx.addIssue({ code: "custom", path: ["set"], message: "revert mutations take no copy/set columns" });
138
+ }
139
+ if (mutation.set.includes(mutation.pk)) {
140
+ ctx.addIssue({ code: "custom", path: ["set"], message: "set must not include the pk \u2014 identity columns are pinned" });
141
+ }
142
+ for (const [field, values] of [["copy", mutation.copy], ["set", mutation.set]]) {
143
+ if (new Set(values).size !== values.length) {
144
+ ctx.addIssue({ code: "custom", path: [field], message: `${field} must not contain duplicate columns` });
145
+ }
146
+ }
147
+ });
148
+ var canonicalTableSchema = zod.z.object({
149
+ table: identifier,
150
+ description: zod.z.string().max(2e3).optional(),
151
+ /** Table-level rename hint (new in v2). */
152
+ renamedFrom: identifier.optional(),
153
+ /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
154
+ source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
155
+ columns: zod.z.array(canonicalColumnSchema).default([]),
156
+ primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
157
+ links: zod.z.array(canonicalLinkSchema).default([]),
158
+ indexes: zod.z.array(canonicalIndexSchema).default([]),
159
+ policies: canonicalPoliciesSchema.default({}),
160
+ /**
161
+ * v1-conversion carry-over only. v2 has no action surface — the runtime
162
+ * neither compiles nor executes these; writes are mutations. Kept parseable
163
+ * so stored definitions and their content hashes stay stable.
164
+ */
165
+ actions: zod.z.array(canonicalActionSchema).default([]),
166
+ /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
167
+ cowMutations: zod.z.array(canonicalCowMutationSchema).optional()
168
+ });
169
+ var canonicalViewSchema = zod.z.object({
170
+ view: identifier,
171
+ description: zod.z.string().max(2e3).optional(),
172
+ columns: zod.z.array(canonicalColumnSchema).min(1),
173
+ /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
174
+ primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
175
+ links: zod.z.array(canonicalLinkSchema).default([]),
176
+ policies: zod.z.object({ read: policyExpressionSchema }).strict(),
177
+ query: zod.z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
178
+ message: "view query must be one SELECT/WITH statement without comments or dollar-quoted bodies"
179
+ }),
180
+ /** Namespace-local logical relation names, sorted for deterministic hashing. */
181
+ dependencies: zod.z.array(identifier).max(500)
182
+ }).superRefine((view, ctx) => {
183
+ for (const [index, column] of view.columns.entries()) {
184
+ if (column.default !== void 0 || column.defaultFn !== void 0) {
185
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare a default` });
186
+ }
187
+ if (column.unique) {
188
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare unique` });
189
+ }
190
+ if (column.renamedFrom !== void 0 || column.cast !== void 0) {
191
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare migration hints` });
192
+ }
193
+ }
194
+ for (const [index, link] of view.links.entries()) {
195
+ if (link.enforce) {
196
+ ctx.addIssue({ code: "custom", path: ["links", index, "enforce"], message: `view link '${link.name}' cannot enforce a foreign key` });
197
+ }
198
+ }
199
+ });
200
+ var canonicalDefinitionSchema = zod.z.object({
201
+ definition_format: zod.z.literal(CANONICAL_FORMAT),
202
+ namespace: identifier.max(41),
203
+ description: zod.z.string().max(2e3).optional(),
204
+ enums: zod.z.array(canonicalEnumSchema).default([]),
205
+ tables: zod.z.array(canonicalTableSchema).min(0).max(500),
206
+ /** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
207
+ views: zod.z.array(canonicalViewSchema).max(500).optional(),
208
+ /** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
209
+ capabilities: zod.z.object({
210
+ roles: zod.z.unknown().optional(),
211
+ uses: zod.z.array(zod.z.string()).default([])
212
+ }).default({ uses: [] })
213
+ }).superRefine((definition, ctx) => {
214
+ const seen = /* @__PURE__ */ new Set();
215
+ for (const [index, table] of definition.tables.entries()) {
216
+ if (seen.has(table.table)) {
217
+ ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
218
+ }
219
+ seen.add(table.table);
220
+ }
221
+ const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
222
+ for (const [tableIndex, table] of definition.tables.entries()) {
223
+ const cowMutations = table.cowMutations ?? [];
224
+ if (cowMutations.length === 0) continue;
225
+ const path = (index, ...rest) => ["tables", tableIndex, "cowMutations", index, ...rest];
226
+ if (table.source !== void 0) {
227
+ ctx.addIssue({ code: "custom", path: path(0), message: `synced table '${table.table}' cannot declare cow mutations \u2014 it is read-only` });
228
+ continue;
229
+ }
230
+ for (const [index, mutation] of cowMutations.entries()) {
231
+ const synced = tableByName.get(mutation.synced);
232
+ if (!synced || synced.source === void 0) {
233
+ ctx.addIssue({
234
+ code: "custom",
235
+ path: path(index, "synced"),
236
+ message: `cow mutation '${mutation.name}' references '${mutation.synced}', which is not a synced table in this namespace`
237
+ });
238
+ continue;
239
+ }
240
+ const overlayColumnsByName = new Map(table.columns.map((column) => [column.name, column]));
241
+ const syncedColumnsByName = new Map(synced.columns.map((column) => [column.name, column]));
242
+ let columnsExist = true;
243
+ for (const property of [mutation.pk, ...mutation.copy, ...mutation.set]) {
244
+ if (!overlayColumnsByName.has(property)) {
245
+ columnsExist = false;
246
+ ctx.addIssue({ code: "custom", path: path(index), message: `cow mutation '${mutation.name}': column '${property}' does not exist on overlay '${table.table}'` });
247
+ }
248
+ }
249
+ for (const property of [mutation.pk, ...mutation.copy]) {
250
+ if (!syncedColumnsByName.has(property)) {
251
+ columnsExist = false;
252
+ ctx.addIssue({ code: "custom", path: path(index), message: `cow mutation '${mutation.name}': column '${property}' does not exist on synced '${mutation.synced}'` });
253
+ }
254
+ }
255
+ if (!columnsExist) continue;
256
+ const overlayPk = table.primaryKey ?? [];
257
+ const syncedPk = synced.primaryKey ?? [];
258
+ if (overlayPk.length !== 1 || overlayPk[0] !== mutation.pk || syncedPk.length !== 1 || syncedPk[0] !== mutation.pk) {
259
+ ctx.addIssue({
260
+ code: "custom",
261
+ path: path(index, "pk"),
262
+ message: `cow mutation '${mutation.name}': pk '${mutation.pk}' must be the single-column primary key on both '${table.table}' and '${mutation.synced}'`
263
+ });
264
+ }
265
+ for (const property of [mutation.pk, ...mutation.copy]) {
266
+ const overlayColumn = overlayColumnsByName.get(property);
267
+ const syncedColumn = syncedColumnsByName.get(property);
268
+ if (!overlayColumn || !syncedColumn) continue;
269
+ if (JSON.stringify(overlayColumn.type) !== JSON.stringify(syncedColumn.type)) {
270
+ ctx.addIssue({
271
+ code: "custom",
272
+ path: path(index),
273
+ message: `cow mutation '${mutation.name}': column '${property}' must have the same type on overlay and synced`
274
+ });
275
+ }
276
+ if (overlayColumn.columnName !== syncedColumn.columnName) {
277
+ ctx.addIssue({
278
+ code: "custom",
279
+ path: path(index),
280
+ message: `cow mutation '${mutation.name}': column '${property}' maps to different Postgres names on overlay ('${overlayColumn.columnName}') and synced ('${syncedColumn.columnName}')`
281
+ });
282
+ }
283
+ if (syncedColumn.nullable !== false && overlayColumn.nullable === false) {
284
+ ctx.addIssue({
285
+ code: "custom",
286
+ path: path(index),
287
+ 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`
288
+ });
289
+ }
290
+ }
291
+ }
292
+ }
293
+ for (const [index, view] of (definition.views ?? []).entries()) {
294
+ if (seen.has(view.view)) {
295
+ ctx.addIssue({ code: "custom", path: ["views", index, "view"], message: `duplicate relation '${view.view}'` });
296
+ }
297
+ seen.add(view.view);
298
+ }
299
+ const views = definition.views ?? [];
300
+ const viewByName = new Map(views.map((view) => [view.view, view]));
301
+ for (const [viewIndex, view] of views.entries()) {
302
+ for (const [dependencyIndex, dependency] of view.dependencies.entries()) {
303
+ if (!seen.has(dependency)) {
304
+ ctx.addIssue({
305
+ code: "custom",
306
+ path: ["views", viewIndex, "dependencies", dependencyIndex],
307
+ message: `view '${view.view}' depends on unknown relation '${dependency}'`
308
+ });
309
+ }
310
+ }
311
+ }
312
+ const visiting = /* @__PURE__ */ new Set();
313
+ const visited = /* @__PURE__ */ new Set();
314
+ let cycleReported = false;
315
+ const visit = (name, path) => {
316
+ if (visited.has(name) || cycleReported) return;
317
+ if (visiting.has(name)) {
318
+ cycleReported = true;
319
+ ctx.addIssue({
320
+ code: "custom",
321
+ path: ["views"],
322
+ message: `view dependencies form a cycle: ${[...path, name].join(" -> ")}`
323
+ });
324
+ return;
325
+ }
326
+ visiting.add(name);
327
+ const view = viewByName.get(name);
328
+ for (const dependency of view?.dependencies ?? []) {
329
+ if (viewByName.has(dependency)) visit(dependency, [...path, name]);
330
+ }
331
+ visiting.delete(name);
332
+ visited.add(name);
333
+ };
334
+ for (const view of views) visit(view.view, []);
335
+ });
336
+ function canonicalRelations(definition) {
337
+ const views = (definition.views ?? []).map((view) => ({
338
+ table: view.view,
339
+ ...view.description !== void 0 ? { description: view.description } : {},
340
+ columns: view.columns,
341
+ ...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
342
+ links: view.links,
343
+ indexes: [],
344
+ policies: { read: view.policies.read },
345
+ actions: []
346
+ }));
347
+ return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
348
+ }
349
+ function normalizeCanonical(definition) {
350
+ const sortByName = (items) => [...items].sort((a, b) => a.name.localeCompare(b.name));
351
+ const normalized = {
352
+ ...definition,
353
+ enums: sortByName(definition.enums),
354
+ tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t) => {
355
+ const { cowMutations, ...rest } = t;
356
+ return {
357
+ ...rest,
358
+ links: sortByName(t.links),
359
+ indexes: [...t.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
360
+ actions: sortByName(t.actions),
361
+ // Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
362
+ ...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
363
+ };
364
+ })
365
+ };
366
+ if (definition.views !== void 0) {
367
+ normalized.views = [...definition.views].sort((a, b) => a.view.localeCompare(b.view)).map((view) => ({
368
+ ...view,
369
+ links: sortByName(view.links),
370
+ dependencies: [...view.dependencies].sort()
371
+ }));
372
+ }
373
+ return normalized;
374
+ }
375
+
376
+ // src/sdl/naming.ts
377
+ function camelCase(name) {
378
+ const segments = name.split("_").filter((segment) => segment.length > 0);
379
+ const first = segments[0];
380
+ if (first === void 0) return name;
381
+ const head = first.charAt(0).toLowerCase() + first.slice(1);
382
+ return head + segments.slice(1).map(capitalizeFirst).join("");
383
+ }
384
+ function pascalCase(name) {
385
+ return capitalizeFirst(camelCase(name));
386
+ }
387
+ function capitalizeFirst(segment) {
388
+ return segment.charAt(0).toUpperCase() + segment.slice(1);
389
+ }
390
+ function singularizeWord(word) {
391
+ if (word.endsWith("ies") && word.length > 3) return `${word.slice(0, -3)}y`;
392
+ for (const suffix of ["sses", "xes", "ches", "shes"]) {
393
+ if (word.endsWith(suffix)) return word.slice(0, -2);
394
+ }
395
+ if (word.endsWith("ss")) return word;
396
+ if (word.endsWith("s") && word.length > 1) return word.slice(0, -1);
397
+ return word;
398
+ }
399
+ function typeName(table) {
400
+ const segments = table.split("_").filter((segment) => segment.length > 0);
401
+ const last = segments[segments.length - 1];
402
+ if (last === void 0) return pascalCase(table);
403
+ return [...segments.slice(0, -1), singularizeWord(last)].map(capitalizeFirst).join("");
404
+ }
405
+ function fieldName(column) {
406
+ return camelCase(column);
407
+ }
408
+ function enumTypeName(name) {
409
+ return pascalCase(name);
410
+ }
411
+ function comparisonExpName(gqlTypeName) {
412
+ return `${capitalizeFirst(gqlTypeName)}ComparisonExp`;
413
+ }
414
+ var SCALAR_FOR_COLUMN_TYPE = {
415
+ uuid: "uuid",
416
+ text: "String",
417
+ integer: "Int",
418
+ bigint: "bigint",
419
+ numeric: "numeric",
420
+ double: "Float",
421
+ boolean: "Boolean",
422
+ timestamptz: "timestamptz",
423
+ date: "date",
424
+ jsonb: "jsonb"
425
+ };
426
+ function graphqlScalarFor(type) {
427
+ return SCALAR_FOR_COLUMN_TYPE[type];
428
+ }
429
+ var COLUMN_TYPE_BY_SCALAR = new Map([
430
+ ["ID", "text"],
431
+ ...COLUMN_TYPES.map((type) => [graphqlScalarFor(type), type])
432
+ ]);
433
+ function columnTypeForScalar(scalarName) {
434
+ return COLUMN_TYPE_BY_SCALAR.get(scalarName);
435
+ }
436
+ var VARIABLE_SCALAR_NAMES = [...COLUMN_TYPE_BY_SCALAR.keys()];
437
+ var CUSTOM_SCALARS = ["bigint", "date", "jsonb", "numeric", "timestamptz", "uuid"];
438
+ var NUMERICISH_COLUMN_TYPES = ["integer", "bigint", "numeric", "double"];
439
+ var ORDER_BY_ENUM = "OrderBy";
440
+ var ORDER_BY_ENUM_VALUES = [
441
+ "ASC",
442
+ "ASC_NULLS_FIRST",
443
+ "ASC_NULLS_LAST",
444
+ "DESC",
445
+ "DESC_NULLS_FIRST",
446
+ "DESC_NULLS_LAST"
447
+ ];
448
+ var PLATFORM_USER_TYPENAME = "PlatformUser";
449
+ function tableNames(table) {
450
+ const root = camelCase(table);
451
+ const rootPascal = pascalCase(table);
452
+ const type = typeName(table);
453
+ return {
454
+ typename: type,
455
+ selectRoot: root,
456
+ byPkRoot: `${root}ByPk`,
457
+ aggregateRoot: `${root}Aggregate`,
458
+ insertRoot: `insert${rootPascal}`,
459
+ insertOneRoot: `insert${rootPascal}One`,
460
+ updateRoot: `update${rootPascal}`,
461
+ updateByPkRoot: `update${rootPascal}ByPk`,
462
+ deleteRoot: `delete${rootPascal}`,
463
+ deleteByPkRoot: `delete${rootPascal}ByPk`,
464
+ boolExp: `${type}BoolExp`,
465
+ orderBy: `${type}OrderBy`,
466
+ selectColumn: `${type}SelectColumn`,
467
+ insertInput: `${type}InsertInput`,
468
+ setInput: `${type}SetInput`,
469
+ appendInput: `${type}AppendInput`,
470
+ incInput: `${type}IncInput`,
471
+ onConflict: `${type}OnConflict`,
472
+ pkColumnsInput: `${type}PkColumnsInput`,
473
+ constraintEnum: `${type}Constraint`,
474
+ updateColumnEnum: `${type}UpdateColumn`,
475
+ mutationResponse: `${type}MutationResponse`,
476
+ aggregate: `${type}Aggregate`,
477
+ aggregateFields: `${type}AggregateFields`,
478
+ sumFields: `${type}SumFields`,
479
+ avgFields: `${type}AvgFields`,
480
+ aggregateBoolExp: `${type}AggregateBoolExp`,
481
+ aggregateBoolExpCount: `${type}AggregateBoolExpCount`
482
+ };
483
+ }
484
+
485
+ // src/sdl/generate.ts
486
+ var SdlGenerationError = class extends Error {
487
+ constructor(message) {
488
+ super(message);
489
+ this.name = "SdlGenerationError";
490
+ }
491
+ };
492
+ var GRAPHQL_NAME_RE = /^[_A-Za-z][_0-9A-Za-z]*$/;
493
+ var RESERVED_ENUM_VALUES = /* @__PURE__ */ new Set(["true", "false", "null"]);
494
+ var NUMERICISH = new Set(NUMERICISH_COLUMN_TYPES);
495
+ var PLATFORM_USER_FIELDS = [
496
+ { name: "sub", gqlType: "String", nullable: false },
497
+ { name: "email", gqlType: "String", nullable: true },
498
+ { name: "name", gqlType: "String", nullable: true },
499
+ { name: "organization", gqlType: "String", nullable: true },
500
+ { name: "role", gqlType: "String", nullable: true },
501
+ { name: "managerId", gqlType: "String", nullable: true },
502
+ { name: "accountEnabled", gqlType: "Boolean", nullable: false }
503
+ ];
504
+ function generateSdl(canonical, options = {}) {
505
+ const def = normalizeCanonical(canonicalDefinitionSchema.parse(canonical));
506
+ const tables = canonicalRelations(def).filter((table) => table.source === void 0);
507
+ if (tables.length === 0) {
508
+ throw new SdlGenerationError(
509
+ `Namespace "${def.namespace}" exposes no relations (synced tables are excluded from SDL in v2.0) \u2014 nothing to generate.`
510
+ );
511
+ }
512
+ for (const table of tables) {
513
+ if (table.columns.length === 0) {
514
+ throw new SdlGenerationError(`Relation "${table.table}" has no columns \u2014 cannot generate an object type.`);
515
+ }
516
+ }
517
+ const namesByTable = new Map(tables.map((table) => [table.table, tableNames(table.table)]));
518
+ const enumByName = new Map(def.enums.map((entry) => [entry.name, entry]));
519
+ const hasPlatformUser = tables.some((table) => table.links.some((link) => link.kind === "toPlatformUser"));
520
+ const usedEnums = /* @__PURE__ */ new Set();
521
+ for (const table of tables) {
522
+ for (const column of table.columns) {
523
+ if (typeof column.type === "object") {
524
+ if (!enumByName.has(column.type.enum)) {
525
+ throw new SdlGenerationError(
526
+ `Column "${table.table}.${column.columnName}" references undeclared enum "${column.type.enum}".`
527
+ );
528
+ }
529
+ usedEnums.add(column.type.enum);
530
+ }
531
+ }
532
+ }
533
+ const aggregateBoolExpTargets = /* @__PURE__ */ new Set();
534
+ for (const table of tables) {
535
+ for (const link of table.links) {
536
+ if (link.kind === "toMany" && link.target !== void 0 && namesByTable.has(link.target)) {
537
+ aggregateBoolExpTargets.add(link.target);
538
+ }
539
+ }
540
+ }
541
+ const usedComparisonScalars = /* @__PURE__ */ new Set();
542
+ for (const table of tables) {
543
+ for (const column of table.columns) {
544
+ if (typeof column.type !== "object") usedComparisonScalars.add(graphqlScalarFor(column.type));
545
+ }
546
+ }
547
+ if (hasPlatformUser) {
548
+ usedComparisonScalars.add("String");
549
+ usedComparisonScalars.add("Boolean");
550
+ }
551
+ if (aggregateBoolExpTargets.size > 0) usedComparisonScalars.add("Int");
552
+ const blocks = [];
553
+ blocks.push(
554
+ `# Generated by @sequenceholdings/orm \u2014 GraphQL SDL for namespace "${def.namespace}".
555
+ # Do not edit: regenerate with \`seq-studio orm generate\`.`
556
+ );
557
+ blocks.push("directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT");
558
+ blocks.push(
559
+ '"""Gates an operation behind a platform capability or custom role \u2014 checked by the data plane before execution."""\ndirective @requires(capability: String, role: String) on QUERY | MUTATION'
560
+ );
561
+ blocks.push("directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT");
562
+ for (const scalar of CUSTOM_SCALARS) blocks.push(`scalar ${scalar}`);
563
+ blocks.push(renderEnum(ORDER_BY_ENUM, [...ORDER_BY_ENUM_VALUES]));
564
+ for (const enumName of [...usedEnums].sort()) {
565
+ const entry = enumByName.get(enumName);
566
+ if (!entry) continue;
567
+ for (const value of entry.values) assertEnumValue(value, `enum "${enumName}"`);
568
+ blocks.push(renderEnum(enumTypeName(enumName), entry.values));
569
+ }
570
+ for (const scalar of [...usedComparisonScalars].sort()) {
571
+ blocks.push(renderComparisonExp(scalar));
572
+ }
573
+ for (const enumName of [...usedEnums].sort()) {
574
+ blocks.push(renderComparisonExp(enumTypeName(enumName)));
575
+ }
576
+ if (hasPlatformUser) blocks.push(...renderPlatformUserBlocks());
577
+ for (const table of tables) {
578
+ const names = namesByTable.get(table.table);
579
+ if (!names) continue;
580
+ blocks.push(
581
+ ...renderTableBlocks({
582
+ table,
583
+ names,
584
+ namesByTable,
585
+ emitAggregateBoolExp: aggregateBoolExpTargets.has(table.table),
586
+ pinned: options.pinnedColumns?.[table.table] ?? []
587
+ })
588
+ );
589
+ }
590
+ blocks.push(renderQueryRoot(tables, namesByTable));
591
+ const mutationRoot = renderMutationRoot(tables, namesByTable);
592
+ if (mutationRoot !== null) blocks.push(mutationRoot);
593
+ return `${blocks.join("\n\n")}
594
+ `;
595
+ }
596
+ function assertEnumValue(value, context) {
597
+ if (!GRAPHQL_NAME_RE.test(value) || RESERVED_ENUM_VALUES.has(value)) {
598
+ throw new SdlGenerationError(
599
+ `Value "${value}" in ${context} is not a valid GraphQL enum value (must match ${GRAPHQL_NAME_RE} and not be true/false/null).`
600
+ );
601
+ }
602
+ }
603
+ function renderEnum(name, values) {
604
+ return `enum ${name} {
605
+ ${values.map((value) => ` ${value}`).join("\n")}
606
+ }`;
607
+ }
608
+ function descriptionLines(text, indent) {
609
+ const escaped = text.replace(/"""/g, '\\"""');
610
+ return [`${indent}"""`, ...escaped.split("\n").map((line) => `${indent}${line}`), `${indent}"""`];
611
+ }
612
+ function renderComparisonExp(gqlTypeName) {
613
+ const fields = [
614
+ ["_eq", gqlTypeName],
615
+ ["_neq", gqlTypeName],
616
+ ["_gt", gqlTypeName],
617
+ ["_gte", gqlTypeName],
618
+ ["_lt", gqlTypeName],
619
+ ["_lte", gqlTypeName],
620
+ ["_in", `[${gqlTypeName}!]`],
621
+ ["_nin", `[${gqlTypeName}!]`],
622
+ ["_isNull", "Boolean"]
623
+ ];
624
+ if (gqlTypeName === "String") {
625
+ fields.push(["_like", "String"], ["_ilike", "String"]);
626
+ }
627
+ if (gqlTypeName === "jsonb") {
628
+ fields.push(["_contains", "jsonb"], ["_hasKey", "String"]);
629
+ }
630
+ fields.sort(([a], [b]) => a.localeCompare(b));
631
+ const body = fields.map(([field, type]) => ` ${field}: ${type}`).join("\n");
632
+ return `input ${comparisonExpName(gqlTypeName)} {
633
+ ${body}
634
+ }`;
635
+ }
636
+ function renderPlatformUserBlocks() {
637
+ const typeFields = PLATFORM_USER_FIELDS.map(
638
+ (field) => ` ${field.name}: ${field.gqlType}${field.nullable ? "" : "!"}`
639
+ ).join("\n");
640
+ const boolExpName = `${PLATFORM_USER_TYPENAME}BoolExp`;
641
+ const boolFields = [
642
+ ` _and: [${boolExpName}!]`,
643
+ ` _not: ${boolExpName}`,
644
+ ` _or: [${boolExpName}!]`,
645
+ ...PLATFORM_USER_FIELDS.map((field) => ` ${field.name}: ${comparisonExpName(field.gqlType)}`)
646
+ ].join("\n");
647
+ const orderFields = PLATFORM_USER_FIELDS.map((field) => ` ${field.name}: ${ORDER_BY_ENUM}`).join("\n");
648
+ return [
649
+ `"""Curated read-only view over platform users (plan \xA74b) \u2014 reachable only through \`toPlatformUser\` links."""
650
+ type ${PLATFORM_USER_TYPENAME} {
651
+ ${typeFields}
652
+ }`,
653
+ `input ${boolExpName} {
654
+ ${boolFields}
655
+ }`,
656
+ `input ${PLATFORM_USER_TYPENAME}OrderBy {
657
+ ${orderFields}
658
+ }`
659
+ ];
660
+ }
661
+ function relationArgs(target) {
662
+ return `where: ${target.boolExp}, orderBy: [${target.orderBy}!], limit: Int, offset: Int`;
663
+ }
664
+ function rootArgs(names) {
665
+ return `where: ${names.boolExp}, orderBy: [${names.orderBy}!], distinctOn: [${names.selectColumn}!], limit: Int, offset: Int`;
666
+ }
667
+ function primaryKeyColumns(table) {
668
+ if (!table.primaryKey || table.primaryKey.length === 0) return [];
669
+ return table.primaryKey.map((identifier2) => {
670
+ const column = table.columns.find((c) => c.columnName === identifier2 || c.name === identifier2);
671
+ if (!column) {
672
+ throw new SdlGenerationError(`Primary key column "${identifier2}" not found on table "${table.table}".`);
673
+ }
674
+ return column;
675
+ });
676
+ }
677
+ function renderTableBlocks(ctx) {
678
+ const { table, names } = ctx;
679
+ const blocks = [
680
+ renderObjectType(ctx),
681
+ renderSelectColumnEnum(ctx),
682
+ renderOrderByInput(ctx),
683
+ renderBoolExp(ctx),
684
+ ...renderAggregateTypes(ctx)
685
+ ];
686
+ if (ctx.emitAggregateBoolExp) blocks.push(...renderAggregateBoolExp(ctx));
687
+ const pk = primaryKeyColumns(table);
688
+ if (pk.length > 0) {
689
+ const fields = pk.map((column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}!`).join("\n");
690
+ blocks.push(`input ${names.pkColumnsInput} {
691
+ ${fields}
692
+ }`);
693
+ }
694
+ if (allowsInsert(table) || allowsUpdate(table)) {
695
+ blocks.push(...renderWriteBlocks(ctx, { insert: allowsInsert(table), update: allowsUpdate(table) }));
696
+ }
697
+ return blocks;
698
+ }
699
+ function allowsInsert(table) {
700
+ return table.policies.write !== void 0 || table.policies.insert !== void 0;
701
+ }
702
+ function allowsUpdate(table) {
703
+ return table.policies.write !== void 0 || table.policies.update !== void 0;
704
+ }
705
+ function allowsOnConflict(table) {
706
+ if (!allowsInsert(table) || !allowsUpdate(table)) return false;
707
+ if (table.policies.insert !== void 0 || table.policies.update !== void 0) return false;
708
+ return table.policies.write !== void 0;
709
+ }
710
+ function renderObjectType(ctx) {
711
+ const { table, names } = ctx;
712
+ const lines = [];
713
+ if (table.description !== void 0) lines.push(...descriptionLines(table.description, ""));
714
+ lines.push(`type ${names.typename} {`);
715
+ for (const column of table.columns) {
716
+ if (column.description !== void 0) lines.push(...descriptionLines(column.description, " "));
717
+ const bang = column.nullable ? "" : "!";
718
+ const deprecated = column.deprecated ? " @deprecated" : "";
719
+ lines.push(` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}${bang}${deprecated}`);
720
+ }
721
+ for (const link of table.links) {
722
+ if (link.description !== void 0) lines.push(...descriptionLines(link.description, " "));
723
+ if (link.kind === "toPlatformUser") {
724
+ lines.push(` ${fieldName(link.name)}: ${PLATFORM_USER_TYPENAME}`);
725
+ continue;
726
+ }
727
+ const target = link.target !== void 0 ? ctx.namesByTable.get(link.target) : void 0;
728
+ if (!target) continue;
729
+ if (link.kind === "toOne") {
730
+ lines.push(` ${fieldName(link.name)}: ${target.typename}`);
731
+ } else {
732
+ lines.push(` ${fieldName(link.name)}(${relationArgs(target)}): [${target.typename}!]!`);
733
+ lines.push(` ${fieldName(link.name)}Aggregate(${relationArgs(target)}): ${target.aggregate}!`);
734
+ }
735
+ }
736
+ lines.push("}");
737
+ return lines.join("\n");
738
+ }
739
+ function renderSelectColumnEnum(ctx) {
740
+ const values = ctx.table.columns.map((column) => fieldName(column.name));
741
+ for (const value of values) assertEnumValue(value, `select-column enum for table "${ctx.table.table}"`);
742
+ return renderEnum(ctx.names.selectColumn, values);
743
+ }
744
+ function renderOrderByInput(ctx) {
745
+ const { table, names } = ctx;
746
+ const lines = table.columns.map((column) => ` ${fieldName(column.name)}: ${ORDER_BY_ENUM}`);
747
+ for (const link of table.links) {
748
+ if (link.kind === "toPlatformUser") {
749
+ lines.push(` ${fieldName(link.name)}: ${PLATFORM_USER_TYPENAME}OrderBy`);
750
+ } else if (link.kind === "toOne" && link.target !== void 0) {
751
+ const target = ctx.namesByTable.get(link.target);
752
+ if (target) lines.push(` ${fieldName(link.name)}: ${target.orderBy}`);
753
+ }
754
+ }
755
+ return `input ${names.orderBy} {
756
+ ${lines.join("\n")}
757
+ }`;
758
+ }
759
+ function renderBoolExp(ctx) {
760
+ const { table, names } = ctx;
761
+ const lines = [
762
+ ` _and: [${names.boolExp}!]`,
763
+ ` _not: ${names.boolExp}`,
764
+ ` _or: [${names.boolExp}!]`
765
+ ];
766
+ for (const column of table.columns) {
767
+ lines.push(` ${fieldName(column.name)}: ${comparisonExpName(columnGqlTypeOf(table, column))}`);
768
+ }
769
+ for (const link of table.links) {
770
+ if (link.kind === "toPlatformUser") {
771
+ lines.push(` ${fieldName(link.name)}: ${PLATFORM_USER_TYPENAME}BoolExp`);
772
+ continue;
773
+ }
774
+ const target = link.target !== void 0 ? ctx.namesByTable.get(link.target) : void 0;
775
+ if (!target) continue;
776
+ lines.push(` ${fieldName(link.name)}: ${target.boolExp}`);
777
+ if (link.kind === "toMany") {
778
+ lines.push(` ${fieldName(link.name)}Aggregate: ${target.aggregateBoolExp}`);
779
+ }
780
+ }
781
+ return `input ${names.boolExp} {
782
+ ${lines.join("\n")}
783
+ }`;
784
+ }
785
+ function numericishColumns(table) {
786
+ return table.columns.filter((column) => typeof column.type !== "object" && NUMERICISH.has(column.type));
787
+ }
788
+ function jsonbColumns(table) {
789
+ return table.columns.filter((column) => column.type === "jsonb");
790
+ }
791
+ function renderAggregateTypes(ctx) {
792
+ const { table, names } = ctx;
793
+ const numericish = numericishColumns(table);
794
+ const blocks = [];
795
+ blocks.push(
796
+ `type ${names.aggregate} {
797
+ aggregate: ${names.aggregateFields}
798
+ nodes: [${names.typename}!]!
799
+ }`
800
+ );
801
+ const fieldLines = [` count(columns: [${names.selectColumn}!], distinct: Boolean): Int!`];
802
+ if (numericish.length > 0) {
803
+ fieldLines.push(` avg: ${names.avgFields}`);
804
+ fieldLines.push(` sum: ${names.sumFields}`);
805
+ }
806
+ blocks.push(`type ${names.aggregateFields} {
807
+ ${fieldLines.join("\n")}
808
+ }`);
809
+ if (numericish.length > 0) {
810
+ const avgLines = numericish.map((column) => ` ${fieldName(column.name)}: Float`);
811
+ blocks.push(`type ${names.avgFields} {
812
+ ${avgLines.join("\n")}
813
+ }`);
814
+ const sumLines = numericish.map((column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`);
815
+ blocks.push(`type ${names.sumFields} {
816
+ ${sumLines.join("\n")}
817
+ }`);
818
+ }
819
+ return blocks;
820
+ }
821
+ function renderAggregateBoolExp(ctx) {
822
+ const { names } = ctx;
823
+ return [
824
+ `input ${names.aggregateBoolExp} {
825
+ count: ${names.aggregateBoolExpCount}
826
+ }`,
827
+ `input ${names.aggregateBoolExpCount} {
828
+ arguments: [${names.selectColumn}!]
829
+ distinct: Boolean
830
+ filter: ${names.boolExp}
831
+ predicate: IntComparisonExp!
832
+ }`
833
+ ];
834
+ }
835
+ function constraintValues(table) {
836
+ const values = /* @__PURE__ */ new Set();
837
+ if (table.primaryKey && table.primaryKey.length > 0) values.add(`${table.table}_pkey`);
838
+ for (const column of table.columns) {
839
+ if (column.unique) values.add(`${table.table}_${column.columnName}_unique`);
840
+ }
841
+ for (const index of table.indexes) {
842
+ if (index.unique) values.add(index.name ?? `${table.table}_${index.columns.join("_")}_key`);
843
+ }
844
+ return [...values].sort();
845
+ }
846
+ function renderWriteBlocks(ctx, access) {
847
+ const { table, names, pinned } = ctx;
848
+ const blocks = [];
849
+ if (access.insert) {
850
+ const pinnedSet = new Set(pinned);
851
+ const insertable = table.columns.filter(
852
+ (column) => !pinnedSet.has(column.name) && !pinnedSet.has(column.columnName)
853
+ );
854
+ if (insertable.length === 0) {
855
+ throw new SdlGenerationError(
856
+ `Table "${table.table}" has no insertable columns after excluding pinned columns [${pinned.join(", ")}].`
857
+ );
858
+ }
859
+ const insertLines = insertable.map(
860
+ (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
861
+ );
862
+ blocks.push(`input ${names.insertInput} {
863
+ ${insertLines.join("\n")}
864
+ }`);
865
+ }
866
+ if (access.update) {
867
+ const setLines = table.columns.map(
868
+ (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
869
+ );
870
+ blocks.push(`input ${names.setInput} {
871
+ ${setLines.join("\n")}
872
+ }`);
873
+ }
874
+ const jsonb = jsonbColumns(table);
875
+ if (access.update && jsonb.length > 0) {
876
+ const appendLines = jsonb.map((column) => ` ${fieldName(column.name)}: jsonb`);
877
+ blocks.push(`input ${names.appendInput} {
878
+ ${appendLines.join("\n")}
879
+ }`);
880
+ }
881
+ const numericish = numericishColumns(table);
882
+ if (access.update && numericish.length > 0) {
883
+ const incLines = numericish.map(
884
+ (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
885
+ );
886
+ blocks.push(`input ${names.incInput} {
887
+ ${incLines.join("\n")}
888
+ }`);
889
+ }
890
+ const constraints = constraintValues(table);
891
+ if (access.insert && allowsOnConflict(table) && constraints.length > 0) {
892
+ for (const value of constraints) assertEnumValue(value, `constraint enum for table "${table.table}"`);
893
+ blocks.push(renderEnum(names.constraintEnum, constraints));
894
+ const updateColumns = table.columns.map((column) => fieldName(column.name));
895
+ blocks.push(renderEnum(names.updateColumnEnum, updateColumns));
896
+ blocks.push(
897
+ `input ${names.onConflict} {
898
+ constraint: ${names.constraintEnum}!
899
+ updateColumns: [${names.updateColumnEnum}!]! = []
900
+ where: ${names.boolExp}
901
+ }`
902
+ );
903
+ }
904
+ if (access.insert || access.update) {
905
+ blocks.push(
906
+ `type ${names.mutationResponse} {
907
+ affectedRows: Int!
908
+ returning: [${names.typename}!]!
909
+ }`
910
+ );
911
+ }
912
+ return blocks;
913
+ }
914
+ function renderQueryRoot(tables, namesByTable) {
915
+ const lines = ["type Query {"];
916
+ for (const table of tables) {
917
+ const names = namesByTable.get(table.table);
918
+ if (!names) continue;
919
+ lines.push(` ${names.selectRoot}(${rootArgs(names)}): [${names.typename}!]!`);
920
+ const pk = primaryKeyColumns(table);
921
+ if (pk.length > 0) {
922
+ const args = pk.map((column) => `${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}!`).join(", ");
923
+ lines.push(` ${names.byPkRoot}(${args}): ${names.typename}`);
924
+ }
925
+ lines.push(` ${names.aggregateRoot}(${rootArgs(names)}): ${names.aggregate}!`);
926
+ }
927
+ lines.push("}");
928
+ return lines.join("\n");
929
+ }
930
+ function columnGqlTypeOf(table, column) {
931
+ return typeof column.type === "object" ? enumTypeName(column.type.enum) : graphqlScalarFor(column.type);
932
+ }
933
+ function renderMutationRoot(tables, namesByTable) {
934
+ const lines = [];
935
+ for (const table of tables) {
936
+ const canInsert = allowsInsert(table);
937
+ const canUpdate = allowsUpdate(table);
938
+ if (!canInsert && !canUpdate) continue;
939
+ const names = namesByTable.get(table.table);
940
+ if (!names) continue;
941
+ const constraints = constraintValues(table);
942
+ const onConflictArg = canInsert && allowsOnConflict(table) && constraints.length > 0 ? `, onConflict: ${names.onConflict}` : "";
943
+ const updateArgs = [`_set: ${names.setInput}`];
944
+ if (jsonbColumns(table).length > 0) updateArgs.push(`_append: ${names.appendInput}`);
945
+ if (numericishColumns(table).length > 0) updateArgs.push(`_inc: ${names.incInput}`);
946
+ const updateArgList = updateArgs.join(", ");
947
+ if (canInsert) {
948
+ lines.push(` ${names.insertRoot}(objects: [${names.insertInput}!]!${onConflictArg}): ${names.mutationResponse}`);
949
+ lines.push(` ${names.insertOneRoot}(object: ${names.insertInput}!${onConflictArg}): ${names.typename}`);
950
+ }
951
+ if (canUpdate) {
952
+ lines.push(` ${names.updateRoot}(where: ${names.boolExp}!, ${updateArgList}): ${names.mutationResponse}`);
953
+ const pk = primaryKeyColumns(table);
954
+ if (pk.length > 0) {
955
+ lines.push(
956
+ ` ${names.updateByPkRoot}(pkColumns: ${names.pkColumnsInput}!, ${updateArgList}): ${names.typename}`
957
+ );
958
+ }
959
+ lines.push(` ${names.deleteRoot}(where: ${names.boolExp}!): ${names.mutationResponse}`);
960
+ if (pk.length > 0) {
961
+ const args = pk.map((column) => `${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}!`).join(", ");
962
+ lines.push(` ${names.deleteByPkRoot}(${args}): ${names.typename}`);
963
+ }
964
+ }
965
+ lines.push(...renderCowMutationFields(table, names));
966
+ }
967
+ if (lines.length === 0) return null;
968
+ return `type Mutation {
969
+ ${lines.join("\n")}
970
+ }`;
971
+ }
972
+ function renderCowMutationFields(table, names) {
973
+ const lines = [];
974
+ const columnByName = new Map(table.columns.map((column) => [column.name, column]));
975
+ for (const mutation of table.cowMutations ?? []) {
976
+ const pkColumn = columnByName.get(mutation.pk);
977
+ if (!pkColumn) continue;
978
+ const pkArg = `${fieldName(mutation.pk)}: ${columnGqlTypeOf(table, pkColumn)}!`;
979
+ if (mutation.kind === "revert") {
980
+ lines.push(` ${fieldName(mutation.name)}(${pkArg}): ${names.typename}`);
981
+ continue;
982
+ }
983
+ const setArgs = mutation.set.map((property) => {
984
+ const column = columnByName.get(property);
985
+ if (!column) return null;
986
+ const bang = column.nullable === false ? "!" : "";
987
+ return `${fieldName(property)}: ${columnGqlTypeOf(table, column)}${bang}`;
988
+ });
989
+ if (setArgs.some((arg) => arg === null)) continue;
990
+ lines.push(` ${fieldName(mutation.name)}(${pkArg}, ${setArgs.join(", ")}): ${names.typename}`);
991
+ }
992
+ return lines;
993
+ }
994
+ function stableJson(value) {
995
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
996
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
997
+ 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)}`);
998
+ return `{${entries.join(",")}}`;
999
+ }
1000
+
1001
+ // src/codegen-gql/errors.ts
1002
+ var CodegenGqlError = class extends Error {
1003
+ constructor(message) {
1004
+ super(message);
1005
+ this.name = "CodegenGqlError";
1006
+ }
1007
+ };
1008
+
1009
+ // src/codegen-gql/manifest.ts
1010
+ var MANIFEST_VERSION = 1;
1011
+ var OP_HASH_RE = /^sha256:[0-9a-f]{64}$/;
1012
+ var GRAPHQL_NAME_RE2 = /^[_A-Za-z][_0-9A-Za-z]*$/;
1013
+ var manifestRequiresSchema = zod.z.object({
1014
+ capability: zod.z.string().min(1).optional(),
1015
+ role: zod.z.string().min(1).optional()
1016
+ }).nullable();
1017
+ var manifestOperationSchema = zod.z.object({
1018
+ hash: zod.z.string().regex(OP_HASH_RE),
1019
+ kind: zod.z.enum(["query", "mutation"]),
1020
+ requires: manifestRequiresSchema
1021
+ });
1022
+ var operationsManifestSchema = zod.z.object({
1023
+ manifestVersion: zod.z.literal(MANIFEST_VERSION),
1024
+ schemaHash: zod.z.string().min(1),
1025
+ opsHash: zod.z.string().regex(OP_HASH_RE),
1026
+ operations: zod.z.record(zod.z.string().regex(GRAPHQL_NAME_RE2), manifestOperationSchema)
1027
+ });
1028
+ function buildOperationsManifest({
1029
+ schemaHash,
1030
+ operations
1031
+ }) {
1032
+ if (schemaHash.length === 0) throw new CodegenGqlError("schemaHash must be a non-empty string.");
1033
+ const sorted = [...operations].sort((a, b) => a.name.localeCompare(b.name));
1034
+ const seen = /* @__PURE__ */ new Set();
1035
+ const entries = sorted.map((op) => {
1036
+ if (!GRAPHQL_NAME_RE2.test(op.name)) {
1037
+ throw new CodegenGqlError(`Operation name "${op.name}" is not a valid GraphQL name.`);
1038
+ }
1039
+ if (seen.has(op.name)) {
1040
+ throw new CodegenGqlError(`Duplicate operation name "${op.name}" \u2014 operation names must be unique per namespace.`);
1041
+ }
1042
+ seen.add(op.name);
1043
+ if (!OP_HASH_RE.test(op.hash)) {
1044
+ throw new CodegenGqlError(`Operation "${op.name}" has malformed hash "${op.hash}" (expected sha256:<64 hex>).`);
1045
+ }
1046
+ return { name: op.name, hash: op.hash, kind: op.kind, requires: normalizeRequires(op.requires) };
1047
+ });
1048
+ const opsHash = `sha256:${crypto.createHash("sha256").update(stableJson(entries)).digest("hex")}`;
1049
+ const operationsRecord = {};
1050
+ for (const entry of entries) {
1051
+ operationsRecord[entry.name] = { hash: entry.hash, kind: entry.kind, requires: entry.requires };
1052
+ }
1053
+ return {
1054
+ manifestVersion: MANIFEST_VERSION,
1055
+ schemaHash,
1056
+ opsHash,
1057
+ operations: operationsRecord
1058
+ };
1059
+ }
1060
+ function normalizeRequires(requires) {
1061
+ if (requires === void 0 || requires === null) return null;
1062
+ const normalized = {};
1063
+ if (requires.capability !== void 0) normalized.capability = requires.capability;
1064
+ if (requires.role !== void 0) normalized.role = requires.role;
1065
+ return normalized.capability === void 0 && normalized.role === void 0 ? null : normalized;
1066
+ }
1067
+
1068
+ // src/codegen-gql/type-policies.ts
1069
+ function generateTypePolicies(canonical) {
1070
+ const def = normalizeCanonical(canonicalDefinitionSchema.parse(canonical));
1071
+ const entries = [];
1072
+ let hasPlatformUser = false;
1073
+ for (const table of canonicalRelations(def)) {
1074
+ if (table.source !== void 0) continue;
1075
+ if (table.links.some((link) => link.kind === "toPlatformUser")) hasPlatformUser = true;
1076
+ if (!table.primaryKey || table.primaryKey.length === 0) continue;
1077
+ const keyFields = table.primaryKey.map((identifier2) => {
1078
+ const column = table.columns.find((c) => c.columnName === identifier2 || c.name === identifier2);
1079
+ if (!column) {
1080
+ throw new CodegenGqlError(`Primary key column "${identifier2}" not found on relation "${table.table}".`);
1081
+ }
1082
+ return fieldName(column.name);
1083
+ });
1084
+ entries.push({ typename: tableNames(table.table).typename, keyFields });
1085
+ }
1086
+ if (hasPlatformUser) entries.push({ typename: PLATFORM_USER_TYPENAME, keyFields: ["sub"] });
1087
+ entries.sort((a, b) => a.typename.localeCompare(b.typename));
1088
+ const body = entries.map((entry) => ` ${entry.typename}: { keyFields: [${entry.keyFields.map((key) => `'${key}'`).join(", ")}] },`).join("\n");
1089
+ return `/* Generated by @sequenceholdings/orm \u2014 Apollo cache type policies. Do not edit. */
1090
+ import type { TypePolicies } from '@apollo/client'
1091
+
1092
+ export const typePolicies: TypePolicies = {
1093
+ ${body}
1094
+ }
1095
+ `;
1096
+ }
1097
+
1098
+ exports.CUSTOM_SCALARS = CUSTOM_SCALARS;
1099
+ exports.MANIFEST_VERSION = MANIFEST_VERSION;
1100
+ exports.NUMERICISH_COLUMN_TYPES = NUMERICISH_COLUMN_TYPES;
1101
+ exports.ORDER_BY_ENUM = ORDER_BY_ENUM;
1102
+ exports.ORDER_BY_ENUM_VALUES = ORDER_BY_ENUM_VALUES;
1103
+ exports.PLATFORM_USER_TYPENAME = PLATFORM_USER_TYPENAME;
1104
+ exports.SdlGenerationError = SdlGenerationError;
1105
+ exports.VARIABLE_SCALAR_NAMES = VARIABLE_SCALAR_NAMES;
1106
+ exports.buildOperationsManifest = buildOperationsManifest;
1107
+ exports.camelCase = camelCase;
1108
+ exports.columnTypeForScalar = columnTypeForScalar;
1109
+ exports.comparisonExpName = comparisonExpName;
1110
+ exports.enumTypeName = enumTypeName;
1111
+ exports.fieldName = fieldName;
1112
+ exports.generateSdl = generateSdl;
1113
+ exports.generateTypePolicies = generateTypePolicies;
1114
+ exports.graphqlScalarFor = graphqlScalarFor;
1115
+ exports.manifestOperationSchema = manifestOperationSchema;
1116
+ exports.manifestRequiresSchema = manifestRequiresSchema;
1117
+ exports.operationsManifestSchema = operationsManifestSchema;
1118
+ exports.pascalCase = pascalCase;
1119
+ exports.tableNames = tableNames;
1120
+ exports.typeName = typeName;
1121
+ //# sourceMappingURL=sdl.cjs.map
1122
+ //# sourceMappingURL=sdl.cjs.map