@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.
- package/README.md +11 -1
- package/dist/authoring.cjs +1066 -59
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +289 -24
- package/dist/authoring.d.ts +289 -24
- package/dist/authoring.js +1056 -60
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +415 -9
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +235 -1
- package/dist/canonical.d.ts +235 -1
- package/dist/canonical.js +411 -10
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +1764 -225
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1765 -226
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +506 -54
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +39 -231
- package/dist/compiler.d.ts +39 -231
- package/dist/compiler.js +504 -55
- package/dist/compiler.js.map +1 -1
- package/dist/drizzle.cjs +14 -0
- package/dist/drizzle.cjs.map +1 -0
- package/dist/drizzle.d.cts +1 -0
- package/dist/drizzle.d.ts +1 -0
- package/dist/drizzle.js +3 -0
- package/dist/drizzle.js.map +1 -0
- package/dist/dynamic-input.cjs +391 -0
- package/dist/dynamic-input.cjs.map +1 -0
- package/dist/dynamic-input.d.cts +111 -0
- package/dist/dynamic-input.d.ts +111 -0
- package/dist/dynamic-input.js +380 -0
- package/dist/dynamic-input.js.map +1 -0
- package/dist/index.cjs +2223 -219
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +125 -9
- package/dist/index.d.ts +125 -9
- package/dist/index.js +2199 -220
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +249 -14
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +20 -80
- package/dist/ir.d.ts +20 -80
- package/dist/ir.js +249 -14
- package/dist/ir.js.map +1 -1
- package/dist/predicate-v2.cjs +1093 -0
- package/dist/predicate-v2.cjs.map +1 -0
- package/dist/predicate-v2.d.cts +139 -0
- package/dist/predicate-v2.d.ts +139 -0
- package/dist/predicate-v2.js +1083 -0
- package/dist/predicate-v2.js.map +1 -0
- package/dist/sdl.cjs +344 -55
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +1 -3
- package/dist/sdl.d.ts +1 -3
- package/dist/sdl.js +344 -55
- package/dist/sdl.js.map +1 -1
- package/package.json +16 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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.
|
|
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
|
|
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(`
|
|
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 (
|
|
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
|
|
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}
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
645
|
-
|
|
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
|
-
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
const
|
|
695
|
-
|
|
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"
|
|
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
|
|
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
|
|
1078
|
+
throw new CodegenGqlError(`Primary key column "${identifier2}" not found on relation "${table.table}".`);
|
|
790
1079
|
}
|
|
791
1080
|
return fieldName(column.name);
|
|
792
1081
|
});
|