@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.cjs
CHANGED
|
@@ -76,8 +76,33 @@ var policyExpressionSchema = zod.z.union([
|
|
|
76
76
|
]);
|
|
77
77
|
var canonicalPoliciesSchema = zod.z.object({
|
|
78
78
|
read: policyExpressionSchema.optional(),
|
|
79
|
-
write: policyExpressionSchema.optional()
|
|
79
|
+
write: policyExpressionSchema.optional(),
|
|
80
|
+
insert: policyExpressionSchema.optional(),
|
|
81
|
+
update: policyExpressionSchema.optional()
|
|
80
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
|
+
}
|
|
81
106
|
var canonicalActionSchema = zod.z.object({
|
|
82
107
|
name: identifier,
|
|
83
108
|
requires: zod.z.string().max(1e3).optional(),
|
|
@@ -89,6 +114,37 @@ var canonicalActionSchema = zod.z.object({
|
|
|
89
114
|
returns: zod.z.enum(["rows", "rowsAffected", "scalar"]).default("rowsAffected"),
|
|
90
115
|
audit: zod.z.object({ action: zod.z.string(), resource: zod.z.string() }).optional()
|
|
91
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
|
+
});
|
|
92
148
|
var canonicalTableSchema = zod.z.object({
|
|
93
149
|
table: identifier,
|
|
94
150
|
description: zod.z.string().max(2e3).optional(),
|
|
@@ -101,7 +157,45 @@ var canonicalTableSchema = zod.z.object({
|
|
|
101
157
|
links: zod.z.array(canonicalLinkSchema).default([]),
|
|
102
158
|
indexes: zod.z.array(canonicalIndexSchema).default([]),
|
|
103
159
|
policies: canonicalPoliciesSchema.default({}),
|
|
104
|
-
|
|
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
|
+
}
|
|
105
199
|
});
|
|
106
200
|
var canonicalDefinitionSchema = zod.z.object({
|
|
107
201
|
definition_format: zod.z.literal(CANONICAL_FORMAT),
|
|
@@ -109,24 +203,174 @@ var canonicalDefinitionSchema = zod.z.object({
|
|
|
109
203
|
description: zod.z.string().max(2e3).optional(),
|
|
110
204
|
enums: zod.z.array(canonicalEnumSchema).default([]),
|
|
111
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(),
|
|
112
208
|
/** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
|
|
113
209
|
capabilities: zod.z.object({
|
|
114
210
|
roles: zod.z.unknown().optional(),
|
|
115
211
|
uses: zod.z.array(zod.z.string()).default([])
|
|
116
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, []);
|
|
117
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
|
+
}
|
|
118
349
|
function normalizeCanonical(definition) {
|
|
119
350
|
const sortByName = (items) => [...items].sort((a, b) => a.name.localeCompare(b.name));
|
|
120
|
-
|
|
351
|
+
const normalized = {
|
|
121
352
|
...definition,
|
|
122
353
|
enums: sortByName(definition.enums),
|
|
123
|
-
tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t) =>
|
|
124
|
-
...t
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
+
})
|
|
129
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;
|
|
130
374
|
}
|
|
131
375
|
|
|
132
376
|
// src/sdl/naming.ts
|
|
@@ -259,15 +503,15 @@ var PLATFORM_USER_FIELDS = [
|
|
|
259
503
|
];
|
|
260
504
|
function generateSdl(canonical, options = {}) {
|
|
261
505
|
const def = normalizeCanonical(canonicalDefinitionSchema.parse(canonical));
|
|
262
|
-
const tables = def.
|
|
506
|
+
const tables = canonicalRelations(def).filter((table) => table.source === void 0);
|
|
263
507
|
if (tables.length === 0) {
|
|
264
508
|
throw new SdlGenerationError(
|
|
265
|
-
`Namespace "${def.namespace}" exposes no
|
|
509
|
+
`Namespace "${def.namespace}" exposes no relations (synced tables are excluded from SDL in v2.0) \u2014 nothing to generate.`
|
|
266
510
|
);
|
|
267
511
|
}
|
|
268
512
|
for (const table of tables) {
|
|
269
513
|
if (table.columns.length === 0) {
|
|
270
|
-
throw new SdlGenerationError(`
|
|
514
|
+
throw new SdlGenerationError(`Relation "${table.table}" has no columns \u2014 cannot generate an object type.`);
|
|
271
515
|
}
|
|
272
516
|
}
|
|
273
517
|
const namesByTable = new Map(tables.map((table) => [table.table, tableNames(table.table)]));
|
|
@@ -447,10 +691,20 @@ function renderTableBlocks(ctx) {
|
|
|
447
691
|
${fields}
|
|
448
692
|
}`);
|
|
449
693
|
}
|
|
450
|
-
if (
|
|
694
|
+
if (allowsInsert(table) || allowsUpdate(table)) {
|
|
695
|
+
blocks.push(...renderWriteBlocks(ctx, { insert: allowsInsert(table), update: allowsUpdate(table) }));
|
|
696
|
+
}
|
|
451
697
|
return blocks;
|
|
452
698
|
}
|
|
453
|
-
function
|
|
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;
|
|
454
708
|
return table.policies.write !== void 0;
|
|
455
709
|
}
|
|
456
710
|
function renderObjectType(ctx) {
|
|
@@ -582,46 +836,50 @@ function constraintValues(table) {
|
|
|
582
836
|
const values = /* @__PURE__ */ new Set();
|
|
583
837
|
if (table.primaryKey && table.primaryKey.length > 0) values.add(`${table.table}_pkey`);
|
|
584
838
|
for (const column of table.columns) {
|
|
585
|
-
if (column.unique) values.add(`${table.table}_${column.columnName}
|
|
839
|
+
if (column.unique) values.add(`${table.table}_${column.columnName}_unique`);
|
|
586
840
|
}
|
|
587
841
|
for (const index of table.indexes) {
|
|
588
842
|
if (index.unique) values.add(index.name ?? `${table.table}_${index.columns.join("_")}_key`);
|
|
589
843
|
}
|
|
590
844
|
return [...values].sort();
|
|
591
845
|
}
|
|
592
|
-
function renderWriteBlocks(ctx) {
|
|
846
|
+
function renderWriteBlocks(ctx, access) {
|
|
593
847
|
const { table, names, pinned } = ctx;
|
|
594
848
|
const blocks = [];
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
if (insertable.length === 0) {
|
|
600
|
-
throw new SdlGenerationError(
|
|
601
|
-
`Table "${table.table}" has no insertable columns after excluding pinned columns [${pinned.join(", ")}].`
|
|
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)
|
|
602
853
|
);
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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} {
|
|
608
863
|
${insertLines.join("\n")}
|
|
609
864
|
}`);
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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} {
|
|
614
871
|
${setLines.join("\n")}
|
|
615
872
|
}`);
|
|
873
|
+
}
|
|
616
874
|
const jsonb = jsonbColumns(table);
|
|
617
|
-
if (jsonb.length > 0) {
|
|
875
|
+
if (access.update && jsonb.length > 0) {
|
|
618
876
|
const appendLines = jsonb.map((column) => ` ${fieldName(column.name)}: jsonb`);
|
|
619
877
|
blocks.push(`input ${names.appendInput} {
|
|
620
878
|
${appendLines.join("\n")}
|
|
621
879
|
}`);
|
|
622
880
|
}
|
|
623
881
|
const numericish = numericishColumns(table);
|
|
624
|
-
if (numericish.length > 0) {
|
|
882
|
+
if (access.update && numericish.length > 0) {
|
|
625
883
|
const incLines = numericish.map(
|
|
626
884
|
(column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
|
|
627
885
|
);
|
|
@@ -630,7 +888,7 @@ ${incLines.join("\n")}
|
|
|
630
888
|
}`);
|
|
631
889
|
}
|
|
632
890
|
const constraints = constraintValues(table);
|
|
633
|
-
if (constraints.length > 0) {
|
|
891
|
+
if (access.insert && allowsOnConflict(table) && constraints.length > 0) {
|
|
634
892
|
for (const value of constraints) assertEnumValue(value, `constraint enum for table "${table.table}"`);
|
|
635
893
|
blocks.push(renderEnum(names.constraintEnum, constraints));
|
|
636
894
|
const updateColumns = table.columns.map((column) => fieldName(column.name));
|
|
@@ -643,12 +901,14 @@ ${incLines.join("\n")}
|
|
|
643
901
|
}`
|
|
644
902
|
);
|
|
645
903
|
}
|
|
646
|
-
|
|
647
|
-
|
|
904
|
+
if (access.insert || access.update) {
|
|
905
|
+
blocks.push(
|
|
906
|
+
`type ${names.mutationResponse} {
|
|
648
907
|
affectedRows: Int!
|
|
649
908
|
returning: [${names.typename}!]!
|
|
650
909
|
}`
|
|
651
|
-
|
|
910
|
+
);
|
|
911
|
+
}
|
|
652
912
|
return blocks;
|
|
653
913
|
}
|
|
654
914
|
function renderQueryRoot(tables, namesByTable) {
|
|
@@ -673,35 +933,64 @@ function columnGqlTypeOf(table, column) {
|
|
|
673
933
|
function renderMutationRoot(tables, namesByTable) {
|
|
674
934
|
const lines = [];
|
|
675
935
|
for (const table of tables) {
|
|
676
|
-
|
|
936
|
+
const canInsert = allowsInsert(table);
|
|
937
|
+
const canUpdate = allowsUpdate(table);
|
|
938
|
+
if (!canInsert && !canUpdate) continue;
|
|
677
939
|
const names = namesByTable.get(table.table);
|
|
678
940
|
if (!names) continue;
|
|
679
941
|
const constraints = constraintValues(table);
|
|
680
|
-
const onConflictArg = constraints.length > 0 ? `, onConflict: ${names.onConflict}` : "";
|
|
942
|
+
const onConflictArg = canInsert && allowsOnConflict(table) && constraints.length > 0 ? `, onConflict: ${names.onConflict}` : "";
|
|
681
943
|
const updateArgs = [`_set: ${names.setInput}`];
|
|
682
944
|
if (jsonbColumns(table).length > 0) updateArgs.push(`_append: ${names.appendInput}`);
|
|
683
945
|
if (numericishColumns(table).length > 0) updateArgs.push(`_inc: ${names.incInput}`);
|
|
684
946
|
const updateArgList = updateArgs.join(", ");
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
const pk = primaryKeyColumns(table);
|
|
689
|
-
if (pk.length > 0) {
|
|
690
|
-
lines.push(
|
|
691
|
-
` ${names.updateByPkRoot}(pkColumns: ${names.pkColumnsInput}!, ${updateArgList}): ${names.typename}`
|
|
692
|
-
);
|
|
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}`);
|
|
693
950
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
const
|
|
697
|
-
|
|
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
|
+
}
|
|
698
964
|
}
|
|
965
|
+
lines.push(...renderCowMutationFields(table, names));
|
|
699
966
|
}
|
|
700
967
|
if (lines.length === 0) return null;
|
|
701
968
|
return `type Mutation {
|
|
702
969
|
${lines.join("\n")}
|
|
703
970
|
}`;
|
|
704
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
|
+
}
|
|
705
994
|
function stableJson(value) {
|
|
706
995
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
707
996
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
@@ -727,7 +1016,7 @@ var manifestRequiresSchema = zod.z.object({
|
|
|
727
1016
|
}).nullable();
|
|
728
1017
|
var manifestOperationSchema = zod.z.object({
|
|
729
1018
|
hash: zod.z.string().regex(OP_HASH_RE),
|
|
730
|
-
kind: zod.z.enum(["query", "mutation"
|
|
1019
|
+
kind: zod.z.enum(["query", "mutation"]),
|
|
731
1020
|
requires: manifestRequiresSchema
|
|
732
1021
|
});
|
|
733
1022
|
var operationsManifestSchema = zod.z.object({
|
|
@@ -781,14 +1070,14 @@ function generateTypePolicies(canonical) {
|
|
|
781
1070
|
const def = normalizeCanonical(canonicalDefinitionSchema.parse(canonical));
|
|
782
1071
|
const entries = [];
|
|
783
1072
|
let hasPlatformUser = false;
|
|
784
|
-
for (const table of def
|
|
1073
|
+
for (const table of canonicalRelations(def)) {
|
|
785
1074
|
if (table.source !== void 0) continue;
|
|
786
1075
|
if (table.links.some((link) => link.kind === "toPlatformUser")) hasPlatformUser = true;
|
|
787
1076
|
if (!table.primaryKey || table.primaryKey.length === 0) continue;
|
|
788
1077
|
const keyFields = table.primaryKey.map((identifier2) => {
|
|
789
1078
|
const column = table.columns.find((c) => c.columnName === identifier2 || c.name === identifier2);
|
|
790
1079
|
if (!column) {
|
|
791
|
-
throw new CodegenGqlError(`Primary key column "${identifier2}" not found on
|
|
1080
|
+
throw new CodegenGqlError(`Primary key column "${identifier2}" not found on relation "${table.table}".`);
|
|
792
1081
|
}
|
|
793
1082
|
return fieldName(column.name);
|
|
794
1083
|
});
|