@sequenceholdings/orm 0.1.2 → 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 (65) hide show
  1. package/README.md +11 -1
  2. package/dist/authoring.cjs +1066 -59
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +289 -24
  5. package/dist/authoring.d.ts +289 -24
  6. package/dist/authoring.js +1056 -60
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +415 -9
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +235 -1
  11. package/dist/canonical.d.ts +235 -1
  12. package/dist/canonical.js +411 -10
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +1764 -225
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +1765 -226
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.cjs.map +1 -1
  19. package/dist/client.d.cts +1 -1
  20. package/dist/client.d.ts +1 -1
  21. package/dist/client.js.map +1 -1
  22. package/dist/compiler.cjs +506 -54
  23. package/dist/compiler.cjs.map +1 -1
  24. package/dist/compiler.d.cts +39 -231
  25. package/dist/compiler.d.ts +39 -231
  26. package/dist/compiler.js +504 -55
  27. package/dist/compiler.js.map +1 -1
  28. package/dist/drizzle.cjs +14 -0
  29. package/dist/drizzle.cjs.map +1 -0
  30. package/dist/drizzle.d.cts +1 -0
  31. package/dist/drizzle.d.ts +1 -0
  32. package/dist/drizzle.js +3 -0
  33. package/dist/drizzle.js.map +1 -0
  34. package/dist/dynamic-input.cjs +391 -0
  35. package/dist/dynamic-input.cjs.map +1 -0
  36. package/dist/dynamic-input.d.cts +111 -0
  37. package/dist/dynamic-input.d.ts +111 -0
  38. package/dist/dynamic-input.js +380 -0
  39. package/dist/dynamic-input.js.map +1 -0
  40. package/dist/index.cjs +2223 -219
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +125 -9
  43. package/dist/index.d.ts +125 -9
  44. package/dist/index.js +2199 -220
  45. package/dist/index.js.map +1 -1
  46. package/dist/ir.cjs +249 -14
  47. package/dist/ir.cjs.map +1 -1
  48. package/dist/ir.d.cts +20 -80
  49. package/dist/ir.d.ts +20 -80
  50. package/dist/ir.js +249 -14
  51. package/dist/ir.js.map +1 -1
  52. package/dist/predicate-v2.cjs +1093 -0
  53. package/dist/predicate-v2.cjs.map +1 -0
  54. package/dist/predicate-v2.d.cts +139 -0
  55. package/dist/predicate-v2.d.ts +139 -0
  56. package/dist/predicate-v2.js +1083 -0
  57. package/dist/predicate-v2.js.map +1 -0
  58. package/dist/sdl.cjs +344 -55
  59. package/dist/sdl.cjs.map +1 -1
  60. package/dist/sdl.d.cts +1 -3
  61. package/dist/sdl.d.ts +1 -3
  62. package/dist/sdl.js +344 -55
  63. package/dist/sdl.js.map +1 -1
  64. package/package.json +16 -1
  65. package/templates/orm-namespace/CLAUDE.md +6 -5
package/dist/sdl.js CHANGED
@@ -74,8 +74,33 @@ var policyExpressionSchema = z.union([
74
74
  ]);
75
75
  var canonicalPoliciesSchema = z.object({
76
76
  read: policyExpressionSchema.optional(),
77
- write: policyExpressionSchema.optional()
77
+ write: policyExpressionSchema.optional(),
78
+ insert: policyExpressionSchema.optional(),
79
+ update: policyExpressionSchema.optional()
78
80
  });
81
+ function isSingleViewQuerySql(value) {
82
+ const query = value.trim();
83
+ if (!/^\(*(?:select|with)\b/i.test(query) || query.includes("\0")) return false;
84
+ let state = "normal";
85
+ for (let index = 0; index < query.length; index += 1) {
86
+ const char = query[index];
87
+ const next = query[index + 1];
88
+ if (state === "single") {
89
+ if (char === "'" && next === "'") index += 1;
90
+ else if (char === "'") state = "normal";
91
+ continue;
92
+ }
93
+ if (state === "double") {
94
+ if (char === '"' && next === '"') index += 1;
95
+ else if (char === '"') state = "normal";
96
+ continue;
97
+ }
98
+ if (char === "'") state = "single";
99
+ else if (char === '"') state = "double";
100
+ else if (char === ";" || char === "-" && next === "-" || char === "/" && next === "*") return false;
101
+ }
102
+ return state === "normal" && !/\$[a-z_0-9]*\$/i.test(query);
103
+ }
79
104
  var canonicalActionSchema = z.object({
80
105
  name: identifier,
81
106
  requires: z.string().max(1e3).optional(),
@@ -87,6 +112,37 @@ var canonicalActionSchema = z.object({
87
112
  returns: z.enum(["rows", "rowsAffected", "scalar"]).default("rowsAffected"),
88
113
  audit: z.object({ action: z.string(), resource: z.string() }).optional()
89
114
  });
115
+ var canonicalCowMutationSchema = z.object({
116
+ kind: z.enum(["override", "revert"]),
117
+ name: identifier,
118
+ /** Synced table (same namespace) the overlay copies from / falls back to. */
119
+ synced: identifier,
120
+ /** Single-column primary key PROPERTY (TS camelCase, like column `name`) shared by overlay and synced. */
121
+ pk: z.string().min(1).max(63),
122
+ /** Override: contract properties copied INSERT…SELECT from synced (pk included). Empty for revert. */
123
+ copy: z.array(z.string().min(1).max(63)).default([]),
124
+ /** Override: overlay properties written by the UPDATE (never the pk). Empty for revert. */
125
+ set: z.array(z.string().min(1).max(63)).default([])
126
+ }).superRefine((mutation, ctx) => {
127
+ if (mutation.kind === "override") {
128
+ if (mutation.set.length === 0) {
129
+ ctx.addIssue({ code: "custom", path: ["set"], message: "override mutations must set at least one column" });
130
+ }
131
+ if (!mutation.copy.includes(mutation.pk)) {
132
+ ctx.addIssue({ code: "custom", path: ["copy"], message: "override copy list must include the pk" });
133
+ }
134
+ } else if (mutation.set.length > 0 || mutation.copy.length > 0) {
135
+ ctx.addIssue({ code: "custom", path: ["set"], message: "revert mutations take no copy/set columns" });
136
+ }
137
+ if (mutation.set.includes(mutation.pk)) {
138
+ ctx.addIssue({ code: "custom", path: ["set"], message: "set must not include the pk \u2014 identity columns are pinned" });
139
+ }
140
+ for (const [field, values] of [["copy", mutation.copy], ["set", mutation.set]]) {
141
+ if (new Set(values).size !== values.length) {
142
+ ctx.addIssue({ code: "custom", path: [field], message: `${field} must not contain duplicate columns` });
143
+ }
144
+ }
145
+ });
90
146
  var canonicalTableSchema = z.object({
91
147
  table: identifier,
92
148
  description: z.string().max(2e3).optional(),
@@ -99,7 +155,45 @@ var canonicalTableSchema = z.object({
99
155
  links: z.array(canonicalLinkSchema).default([]),
100
156
  indexes: z.array(canonicalIndexSchema).default([]),
101
157
  policies: canonicalPoliciesSchema.default({}),
102
- actions: z.array(canonicalActionSchema).default([])
158
+ /**
159
+ * v1-conversion carry-over only. v2 has no action surface — the runtime
160
+ * neither compiles nor executes these; writes are mutations. Kept parseable
161
+ * so stored definitions and their content hashes stay stable.
162
+ */
163
+ actions: z.array(canonicalActionSchema).default([]),
164
+ /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
165
+ cowMutations: z.array(canonicalCowMutationSchema).optional()
166
+ });
167
+ var canonicalViewSchema = z.object({
168
+ view: identifier,
169
+ description: z.string().max(2e3).optional(),
170
+ columns: z.array(canonicalColumnSchema).min(1),
171
+ /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
172
+ primaryKey: z.array(identifier).min(1).max(8).optional(),
173
+ links: z.array(canonicalLinkSchema).default([]),
174
+ policies: z.object({ read: policyExpressionSchema }).strict(),
175
+ query: z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
176
+ message: "view query must be one SELECT/WITH statement without comments or dollar-quoted bodies"
177
+ }),
178
+ /** Namespace-local logical relation names, sorted for deterministic hashing. */
179
+ dependencies: z.array(identifier).max(500)
180
+ }).superRefine((view, ctx) => {
181
+ for (const [index, column] of view.columns.entries()) {
182
+ if (column.default !== void 0 || column.defaultFn !== void 0) {
183
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare a default` });
184
+ }
185
+ if (column.unique) {
186
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare unique` });
187
+ }
188
+ if (column.renamedFrom !== void 0 || column.cast !== void 0) {
189
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare migration hints` });
190
+ }
191
+ }
192
+ for (const [index, link] of view.links.entries()) {
193
+ if (link.enforce) {
194
+ ctx.addIssue({ code: "custom", path: ["links", index, "enforce"], message: `view link '${link.name}' cannot enforce a foreign key` });
195
+ }
196
+ }
103
197
  });
104
198
  var canonicalDefinitionSchema = z.object({
105
199
  definition_format: z.literal(CANONICAL_FORMAT),
@@ -107,24 +201,174 @@ var canonicalDefinitionSchema = z.object({
107
201
  description: z.string().max(2e3).optional(),
108
202
  enums: z.array(canonicalEnumSchema).default([]),
109
203
  tables: z.array(canonicalTableSchema).min(0).max(500),
204
+ /** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
205
+ views: z.array(canonicalViewSchema).max(500).optional(),
110
206
  /** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
111
207
  capabilities: z.object({
112
208
  roles: z.unknown().optional(),
113
209
  uses: z.array(z.string()).default([])
114
210
  }).default({ uses: [] })
211
+ }).superRefine((definition, ctx) => {
212
+ const seen = /* @__PURE__ */ new Set();
213
+ for (const [index, table] of definition.tables.entries()) {
214
+ if (seen.has(table.table)) {
215
+ ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
216
+ }
217
+ seen.add(table.table);
218
+ }
219
+ const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
220
+ for (const [tableIndex, table] of definition.tables.entries()) {
221
+ const cowMutations = table.cowMutations ?? [];
222
+ if (cowMutations.length === 0) continue;
223
+ const path = (index, ...rest) => ["tables", tableIndex, "cowMutations", index, ...rest];
224
+ if (table.source !== void 0) {
225
+ ctx.addIssue({ code: "custom", path: path(0), message: `synced table '${table.table}' cannot declare cow mutations \u2014 it is read-only` });
226
+ continue;
227
+ }
228
+ for (const [index, mutation] of cowMutations.entries()) {
229
+ const synced = tableByName.get(mutation.synced);
230
+ if (!synced || synced.source === void 0) {
231
+ ctx.addIssue({
232
+ code: "custom",
233
+ path: path(index, "synced"),
234
+ message: `cow mutation '${mutation.name}' references '${mutation.synced}', which is not a synced table in this namespace`
235
+ });
236
+ continue;
237
+ }
238
+ const overlayColumnsByName = new Map(table.columns.map((column) => [column.name, column]));
239
+ const syncedColumnsByName = new Map(synced.columns.map((column) => [column.name, column]));
240
+ let columnsExist = true;
241
+ for (const property of [mutation.pk, ...mutation.copy, ...mutation.set]) {
242
+ if (!overlayColumnsByName.has(property)) {
243
+ columnsExist = false;
244
+ ctx.addIssue({ code: "custom", path: path(index), message: `cow mutation '${mutation.name}': column '${property}' does not exist on overlay '${table.table}'` });
245
+ }
246
+ }
247
+ for (const property of [mutation.pk, ...mutation.copy]) {
248
+ if (!syncedColumnsByName.has(property)) {
249
+ columnsExist = false;
250
+ ctx.addIssue({ code: "custom", path: path(index), message: `cow mutation '${mutation.name}': column '${property}' does not exist on synced '${mutation.synced}'` });
251
+ }
252
+ }
253
+ if (!columnsExist) continue;
254
+ const overlayPk = table.primaryKey ?? [];
255
+ const syncedPk = synced.primaryKey ?? [];
256
+ if (overlayPk.length !== 1 || overlayPk[0] !== mutation.pk || syncedPk.length !== 1 || syncedPk[0] !== mutation.pk) {
257
+ ctx.addIssue({
258
+ code: "custom",
259
+ path: path(index, "pk"),
260
+ message: `cow mutation '${mutation.name}': pk '${mutation.pk}' must be the single-column primary key on both '${table.table}' and '${mutation.synced}'`
261
+ });
262
+ }
263
+ for (const property of [mutation.pk, ...mutation.copy]) {
264
+ const overlayColumn = overlayColumnsByName.get(property);
265
+ const syncedColumn = syncedColumnsByName.get(property);
266
+ if (!overlayColumn || !syncedColumn) continue;
267
+ if (JSON.stringify(overlayColumn.type) !== JSON.stringify(syncedColumn.type)) {
268
+ ctx.addIssue({
269
+ code: "custom",
270
+ path: path(index),
271
+ message: `cow mutation '${mutation.name}': column '${property}' must have the same type on overlay and synced`
272
+ });
273
+ }
274
+ if (overlayColumn.columnName !== syncedColumn.columnName) {
275
+ ctx.addIssue({
276
+ code: "custom",
277
+ path: path(index),
278
+ message: `cow mutation '${mutation.name}': column '${property}' maps to different Postgres names on overlay ('${overlayColumn.columnName}') and synced ('${syncedColumn.columnName}')`
279
+ });
280
+ }
281
+ if (syncedColumn.nullable !== false && overlayColumn.nullable === false) {
282
+ ctx.addIssue({
283
+ code: "custom",
284
+ path: path(index),
285
+ 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`
286
+ });
287
+ }
288
+ }
289
+ }
290
+ }
291
+ for (const [index, view] of (definition.views ?? []).entries()) {
292
+ if (seen.has(view.view)) {
293
+ ctx.addIssue({ code: "custom", path: ["views", index, "view"], message: `duplicate relation '${view.view}'` });
294
+ }
295
+ seen.add(view.view);
296
+ }
297
+ const views = definition.views ?? [];
298
+ const viewByName = new Map(views.map((view) => [view.view, view]));
299
+ for (const [viewIndex, view] of views.entries()) {
300
+ for (const [dependencyIndex, dependency] of view.dependencies.entries()) {
301
+ if (!seen.has(dependency)) {
302
+ ctx.addIssue({
303
+ code: "custom",
304
+ path: ["views", viewIndex, "dependencies", dependencyIndex],
305
+ message: `view '${view.view}' depends on unknown relation '${dependency}'`
306
+ });
307
+ }
308
+ }
309
+ }
310
+ const visiting = /* @__PURE__ */ new Set();
311
+ const visited = /* @__PURE__ */ new Set();
312
+ let cycleReported = false;
313
+ const visit = (name, path) => {
314
+ if (visited.has(name) || cycleReported) return;
315
+ if (visiting.has(name)) {
316
+ cycleReported = true;
317
+ ctx.addIssue({
318
+ code: "custom",
319
+ path: ["views"],
320
+ message: `view dependencies form a cycle: ${[...path, name].join(" -> ")}`
321
+ });
322
+ return;
323
+ }
324
+ visiting.add(name);
325
+ const view = viewByName.get(name);
326
+ for (const dependency of view?.dependencies ?? []) {
327
+ if (viewByName.has(dependency)) visit(dependency, [...path, name]);
328
+ }
329
+ visiting.delete(name);
330
+ visited.add(name);
331
+ };
332
+ for (const view of views) visit(view.view, []);
115
333
  });
334
+ function canonicalRelations(definition) {
335
+ const views = (definition.views ?? []).map((view) => ({
336
+ table: view.view,
337
+ ...view.description !== void 0 ? { description: view.description } : {},
338
+ columns: view.columns,
339
+ ...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
340
+ links: view.links,
341
+ indexes: [],
342
+ policies: { read: view.policies.read },
343
+ actions: []
344
+ }));
345
+ return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
346
+ }
116
347
  function normalizeCanonical(definition) {
117
348
  const sortByName = (items) => [...items].sort((a, b) => a.name.localeCompare(b.name));
118
- return {
349
+ const normalized = {
119
350
  ...definition,
120
351
  enums: sortByName(definition.enums),
121
- tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t) => ({
122
- ...t,
123
- links: sortByName(t.links),
124
- indexes: [...t.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
125
- actions: sortByName(t.actions)
126
- }))
352
+ tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t) => {
353
+ const { cowMutations, ...rest } = t;
354
+ return {
355
+ ...rest,
356
+ links: sortByName(t.links),
357
+ indexes: [...t.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
358
+ actions: sortByName(t.actions),
359
+ // Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
360
+ ...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
361
+ };
362
+ })
127
363
  };
364
+ if (definition.views !== void 0) {
365
+ normalized.views = [...definition.views].sort((a, b) => a.view.localeCompare(b.view)).map((view) => ({
366
+ ...view,
367
+ links: sortByName(view.links),
368
+ dependencies: [...view.dependencies].sort()
369
+ }));
370
+ }
371
+ return normalized;
128
372
  }
129
373
 
130
374
  // src/sdl/naming.ts
@@ -257,15 +501,15 @@ var PLATFORM_USER_FIELDS = [
257
501
  ];
258
502
  function generateSdl(canonical, options = {}) {
259
503
  const def = normalizeCanonical(canonicalDefinitionSchema.parse(canonical));
260
- const tables = def.tables.filter((table) => table.source === void 0);
504
+ const tables = canonicalRelations(def).filter((table) => table.source === void 0);
261
505
  if (tables.length === 0) {
262
506
  throw new SdlGenerationError(
263
- `Namespace "${def.namespace}" exposes no tables (synced tables are excluded from SDL in v2.0) \u2014 nothing to generate.`
507
+ `Namespace "${def.namespace}" exposes no relations (synced tables are excluded from SDL in v2.0) \u2014 nothing to generate.`
264
508
  );
265
509
  }
266
510
  for (const table of tables) {
267
511
  if (table.columns.length === 0) {
268
- throw new SdlGenerationError(`Table "${table.table}" has no columns \u2014 cannot generate an object type.`);
512
+ throw new SdlGenerationError(`Relation "${table.table}" has no columns \u2014 cannot generate an object type.`);
269
513
  }
270
514
  }
271
515
  const namesByTable = new Map(tables.map((table) => [table.table, tableNames(table.table)]));
@@ -445,10 +689,20 @@ function renderTableBlocks(ctx) {
445
689
  ${fields}
446
690
  }`);
447
691
  }
448
- if (isWritable(table)) blocks.push(...renderWriteBlocks(ctx));
692
+ if (allowsInsert(table) || allowsUpdate(table)) {
693
+ blocks.push(...renderWriteBlocks(ctx, { insert: allowsInsert(table), update: allowsUpdate(table) }));
694
+ }
449
695
  return blocks;
450
696
  }
451
- function isWritable(table) {
697
+ function allowsInsert(table) {
698
+ return table.policies.write !== void 0 || table.policies.insert !== void 0;
699
+ }
700
+ function allowsUpdate(table) {
701
+ return table.policies.write !== void 0 || table.policies.update !== void 0;
702
+ }
703
+ function allowsOnConflict(table) {
704
+ if (!allowsInsert(table) || !allowsUpdate(table)) return false;
705
+ if (table.policies.insert !== void 0 || table.policies.update !== void 0) return false;
452
706
  return table.policies.write !== void 0;
453
707
  }
454
708
  function renderObjectType(ctx) {
@@ -580,46 +834,50 @@ function constraintValues(table) {
580
834
  const values = /* @__PURE__ */ new Set();
581
835
  if (table.primaryKey && table.primaryKey.length > 0) values.add(`${table.table}_pkey`);
582
836
  for (const column of table.columns) {
583
- if (column.unique) values.add(`${table.table}_${column.columnName}_key`);
837
+ if (column.unique) values.add(`${table.table}_${column.columnName}_unique`);
584
838
  }
585
839
  for (const index of table.indexes) {
586
840
  if (index.unique) values.add(index.name ?? `${table.table}_${index.columns.join("_")}_key`);
587
841
  }
588
842
  return [...values].sort();
589
843
  }
590
- function renderWriteBlocks(ctx) {
844
+ function renderWriteBlocks(ctx, access) {
591
845
  const { table, names, pinned } = ctx;
592
846
  const blocks = [];
593
- const pinnedSet = new Set(pinned);
594
- const insertable = table.columns.filter(
595
- (column) => !pinnedSet.has(column.name) && !pinnedSet.has(column.columnName)
596
- );
597
- if (insertable.length === 0) {
598
- throw new SdlGenerationError(
599
- `Table "${table.table}" has no insertable columns after excluding pinned columns [${pinned.join(", ")}].`
847
+ if (access.insert) {
848
+ const pinnedSet = new Set(pinned);
849
+ const insertable = table.columns.filter(
850
+ (column) => !pinnedSet.has(column.name) && !pinnedSet.has(column.columnName)
600
851
  );
601
- }
602
- const insertLines = insertable.map(
603
- (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
604
- );
605
- blocks.push(`input ${names.insertInput} {
852
+ if (insertable.length === 0) {
853
+ throw new SdlGenerationError(
854
+ `Table "${table.table}" has no insertable columns after excluding pinned columns [${pinned.join(", ")}].`
855
+ );
856
+ }
857
+ const insertLines = insertable.map(
858
+ (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
859
+ );
860
+ blocks.push(`input ${names.insertInput} {
606
861
  ${insertLines.join("\n")}
607
862
  }`);
608
- const setLines = table.columns.map(
609
- (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
610
- );
611
- blocks.push(`input ${names.setInput} {
863
+ }
864
+ if (access.update) {
865
+ const setLines = table.columns.map(
866
+ (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
867
+ );
868
+ blocks.push(`input ${names.setInput} {
612
869
  ${setLines.join("\n")}
613
870
  }`);
871
+ }
614
872
  const jsonb = jsonbColumns(table);
615
- if (jsonb.length > 0) {
873
+ if (access.update && jsonb.length > 0) {
616
874
  const appendLines = jsonb.map((column) => ` ${fieldName(column.name)}: jsonb`);
617
875
  blocks.push(`input ${names.appendInput} {
618
876
  ${appendLines.join("\n")}
619
877
  }`);
620
878
  }
621
879
  const numericish = numericishColumns(table);
622
- if (numericish.length > 0) {
880
+ if (access.update && numericish.length > 0) {
623
881
  const incLines = numericish.map(
624
882
  (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
625
883
  );
@@ -628,7 +886,7 @@ ${incLines.join("\n")}
628
886
  }`);
629
887
  }
630
888
  const constraints = constraintValues(table);
631
- if (constraints.length > 0) {
889
+ if (access.insert && allowsOnConflict(table) && constraints.length > 0) {
632
890
  for (const value of constraints) assertEnumValue(value, `constraint enum for table "${table.table}"`);
633
891
  blocks.push(renderEnum(names.constraintEnum, constraints));
634
892
  const updateColumns = table.columns.map((column) => fieldName(column.name));
@@ -641,12 +899,14 @@ ${incLines.join("\n")}
641
899
  }`
642
900
  );
643
901
  }
644
- blocks.push(
645
- `type ${names.mutationResponse} {
902
+ if (access.insert || access.update) {
903
+ blocks.push(
904
+ `type ${names.mutationResponse} {
646
905
  affectedRows: Int!
647
906
  returning: [${names.typename}!]!
648
907
  }`
649
- );
908
+ );
909
+ }
650
910
  return blocks;
651
911
  }
652
912
  function renderQueryRoot(tables, namesByTable) {
@@ -671,35 +931,64 @@ function columnGqlTypeOf(table, column) {
671
931
  function renderMutationRoot(tables, namesByTable) {
672
932
  const lines = [];
673
933
  for (const table of tables) {
674
- if (!isWritable(table)) continue;
934
+ const canInsert = allowsInsert(table);
935
+ const canUpdate = allowsUpdate(table);
936
+ if (!canInsert && !canUpdate) continue;
675
937
  const names = namesByTable.get(table.table);
676
938
  if (!names) continue;
677
939
  const constraints = constraintValues(table);
678
- const onConflictArg = constraints.length > 0 ? `, onConflict: ${names.onConflict}` : "";
940
+ const onConflictArg = canInsert && allowsOnConflict(table) && constraints.length > 0 ? `, onConflict: ${names.onConflict}` : "";
679
941
  const updateArgs = [`_set: ${names.setInput}`];
680
942
  if (jsonbColumns(table).length > 0) updateArgs.push(`_append: ${names.appendInput}`);
681
943
  if (numericishColumns(table).length > 0) updateArgs.push(`_inc: ${names.incInput}`);
682
944
  const updateArgList = updateArgs.join(", ");
683
- lines.push(` ${names.insertRoot}(objects: [${names.insertInput}!]!${onConflictArg}): ${names.mutationResponse}`);
684
- lines.push(` ${names.insertOneRoot}(object: ${names.insertInput}!${onConflictArg}): ${names.typename}`);
685
- lines.push(` ${names.updateRoot}(where: ${names.boolExp}!, ${updateArgList}): ${names.mutationResponse}`);
686
- const pk = primaryKeyColumns(table);
687
- if (pk.length > 0) {
688
- lines.push(
689
- ` ${names.updateByPkRoot}(pkColumns: ${names.pkColumnsInput}!, ${updateArgList}): ${names.typename}`
690
- );
945
+ if (canInsert) {
946
+ lines.push(` ${names.insertRoot}(objects: [${names.insertInput}!]!${onConflictArg}): ${names.mutationResponse}`);
947
+ lines.push(` ${names.insertOneRoot}(object: ${names.insertInput}!${onConflictArg}): ${names.typename}`);
691
948
  }
692
- lines.push(` ${names.deleteRoot}(where: ${names.boolExp}!): ${names.mutationResponse}`);
693
- if (pk.length > 0) {
694
- const args = pk.map((column) => `${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}!`).join(", ");
695
- lines.push(` ${names.deleteByPkRoot}(${args}): ${names.typename}`);
949
+ if (canUpdate) {
950
+ lines.push(` ${names.updateRoot}(where: ${names.boolExp}!, ${updateArgList}): ${names.mutationResponse}`);
951
+ const pk = primaryKeyColumns(table);
952
+ if (pk.length > 0) {
953
+ lines.push(
954
+ ` ${names.updateByPkRoot}(pkColumns: ${names.pkColumnsInput}!, ${updateArgList}): ${names.typename}`
955
+ );
956
+ }
957
+ lines.push(` ${names.deleteRoot}(where: ${names.boolExp}!): ${names.mutationResponse}`);
958
+ if (pk.length > 0) {
959
+ const args = pk.map((column) => `${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}!`).join(", ");
960
+ lines.push(` ${names.deleteByPkRoot}(${args}): ${names.typename}`);
961
+ }
696
962
  }
963
+ lines.push(...renderCowMutationFields(table, names));
697
964
  }
698
965
  if (lines.length === 0) return null;
699
966
  return `type Mutation {
700
967
  ${lines.join("\n")}
701
968
  }`;
702
969
  }
970
+ function renderCowMutationFields(table, names) {
971
+ const lines = [];
972
+ const columnByName = new Map(table.columns.map((column) => [column.name, column]));
973
+ for (const mutation of table.cowMutations ?? []) {
974
+ const pkColumn = columnByName.get(mutation.pk);
975
+ if (!pkColumn) continue;
976
+ const pkArg = `${fieldName(mutation.pk)}: ${columnGqlTypeOf(table, pkColumn)}!`;
977
+ if (mutation.kind === "revert") {
978
+ lines.push(` ${fieldName(mutation.name)}(${pkArg}): ${names.typename}`);
979
+ continue;
980
+ }
981
+ const setArgs = mutation.set.map((property) => {
982
+ const column = columnByName.get(property);
983
+ if (!column) return null;
984
+ const bang = column.nullable === false ? "!" : "";
985
+ return `${fieldName(property)}: ${columnGqlTypeOf(table, column)}${bang}`;
986
+ });
987
+ if (setArgs.some((arg) => arg === null)) continue;
988
+ lines.push(` ${fieldName(mutation.name)}(${pkArg}, ${setArgs.join(", ")}): ${names.typename}`);
989
+ }
990
+ return lines;
991
+ }
703
992
  function stableJson(value) {
704
993
  if (value === null || typeof value !== "object") return JSON.stringify(value);
705
994
  if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
@@ -725,7 +1014,7 @@ var manifestRequiresSchema = z.object({
725
1014
  }).nullable();
726
1015
  var manifestOperationSchema = z.object({
727
1016
  hash: z.string().regex(OP_HASH_RE),
728
- kind: z.enum(["query", "mutation", "action"]),
1017
+ kind: z.enum(["query", "mutation"]),
729
1018
  requires: manifestRequiresSchema
730
1019
  });
731
1020
  var operationsManifestSchema = z.object({
@@ -779,14 +1068,14 @@ function generateTypePolicies(canonical) {
779
1068
  const def = normalizeCanonical(canonicalDefinitionSchema.parse(canonical));
780
1069
  const entries = [];
781
1070
  let hasPlatformUser = false;
782
- for (const table of def.tables) {
1071
+ for (const table of canonicalRelations(def)) {
783
1072
  if (table.source !== void 0) continue;
784
1073
  if (table.links.some((link) => link.kind === "toPlatformUser")) hasPlatformUser = true;
785
1074
  if (!table.primaryKey || table.primaryKey.length === 0) continue;
786
1075
  const keyFields = table.primaryKey.map((identifier2) => {
787
1076
  const column = table.columns.find((c) => c.columnName === identifier2 || c.name === identifier2);
788
1077
  if (!column) {
789
- throw new CodegenGqlError(`Primary key column "${identifier2}" not found on table "${table.table}".`);
1078
+ throw new CodegenGqlError(`Primary key column "${identifier2}" not found on relation "${table.table}".`);
790
1079
  }
791
1080
  return fieldName(column.name);
792
1081
  });