@sequenceholdings/orm 2.0.0 → 2.1.1
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 +94 -2
- package/dist/authoring.cjs +1474 -54
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +242 -4
- package/dist/authoring.d.ts +242 -4
- package/dist/authoring.js +1465 -56
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +740 -42
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +742 -53
- package/dist/canonical.d.ts +742 -53
- package/dist/canonical.js +724 -43
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +2478 -328
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2480 -330
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs +90 -25
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +39 -22
- package/dist/client.d.ts +39 -22
- package/dist/client.js +87 -26
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +1755 -329
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +56 -3
- package/dist/compiler.d.ts +56 -3
- package/dist/compiler.js +1756 -330
- package/dist/compiler.js.map +1 -1
- package/dist/constants-YoFGjIBM.d.cts +99 -0
- package/dist/constants-YoFGjIBM.d.ts +99 -0
- package/dist/dynamic-input.d.cts +1 -0
- package/dist/dynamic-input.d.ts +1 -0
- package/dist/index.cjs +1973 -164
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +474 -100
- package/dist/index.d.ts +474 -100
- package/dist/index.js +1940 -167
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +869 -46
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +699 -25
- package/dist/ir.d.ts +699 -25
- package/dist/ir.js +861 -47
- package/dist/ir.js.map +1 -1
- package/dist/{naming-CTvWSRTf.d.ts → naming-CPfx7EfF.d.ts} +28 -1
- package/dist/{naming-DRyinU4u.d.cts → naming-CU9ThP_E.d.cts} +28 -1
- package/dist/policy-1iZbGnZZ.d.cts +24 -0
- package/dist/policy-1iZbGnZZ.d.ts +24 -0
- package/dist/predicate-v2.cjs +852 -49
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.d.cts +2 -1
- package/dist/predicate-v2.d.ts +2 -1
- package/dist/predicate-v2.js +852 -49
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +1309 -58
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +7 -3
- package/dist/sdl.d.ts +7 -3
- package/dist/sdl.js +1305 -59
- package/dist/sdl.js.map +1 -1
- package/package.json +11 -6
- package/templates/orm-namespace/CLAUDE.md +6 -4
- package/templates/orm-namespace/codegen.preset.d.cts +7 -0
- package/templates/orm-namespace/codegen.ts +2 -1
- package/templates/orm-namespace/graphql/example/queries.ts +4 -3
- package/templates/orm-namespace/package.json +2 -1
- package/templates/orm-namespace/schema/tables.ts +3 -2
- package/templates/orm-namespace/sequence.config.ts +7 -11
package/dist/sdl.cjs
CHANGED
|
@@ -3,10 +3,481 @@
|
|
|
3
3
|
var zod = require('zod');
|
|
4
4
|
var crypto = require('crypto');
|
|
5
5
|
|
|
6
|
+
// src/canonical/v2.ts
|
|
7
|
+
var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
8
|
+
var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
9
|
+
|
|
10
|
+
// src/checks/compile.ts
|
|
11
|
+
function tokenize(source) {
|
|
12
|
+
const tokens = [];
|
|
13
|
+
for (let position = 0; position < source.length; ) {
|
|
14
|
+
const char = source[position];
|
|
15
|
+
if (/\s/.test(char)) {
|
|
16
|
+
position++;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (char === "(" || char === ")" || char === ",") {
|
|
20
|
+
tokens.push({ kind: char === "(" ? "lparen" : char === ")" ? "rparen" : "comma", value: char, position });
|
|
21
|
+
position++;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (char === "'") {
|
|
25
|
+
const start = position++;
|
|
26
|
+
let value = "";
|
|
27
|
+
let closed = false;
|
|
28
|
+
while (position < source.length) {
|
|
29
|
+
if (source[position] !== "'") {
|
|
30
|
+
value += source[position++];
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (source[position + 1] === "'") {
|
|
34
|
+
value += "'";
|
|
35
|
+
position += 2;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
position++;
|
|
39
|
+
closed = true;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
if (!closed) throw new Error(`unterminated string at position ${start}`);
|
|
43
|
+
if (/[\x00-\x1f\x7f]/.test(value)) throw new Error(`control character in string at position ${start}`);
|
|
44
|
+
tokens.push({ kind: "string", value, position: start });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const operator = /^(<=|>=|<>|!=|!~|=|<|>|~)/.exec(source.slice(position));
|
|
48
|
+
if (operator) {
|
|
49
|
+
tokens.push({ kind: "operator", value: operator[0] === "<>" ? "!=" : operator[0], position });
|
|
50
|
+
position += operator[0].length;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const number = /^-?\d+(?:\.\d+)?/.exec(source.slice(position));
|
|
54
|
+
if (number) {
|
|
55
|
+
tokens.push({ kind: "number", value: number[0], position });
|
|
56
|
+
position += number[0].length;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const identifier3 = /^[a-zA-Z_][a-zA-Z_0-9]*/.exec(source.slice(position));
|
|
60
|
+
if (identifier3) {
|
|
61
|
+
tokens.push({ kind: "identifier", value: identifier3[0], position });
|
|
62
|
+
position += identifier3[0].length;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`unexpected character '${char}' at position ${position}`);
|
|
66
|
+
}
|
|
67
|
+
tokens.push({ kind: "end", value: "", position: source.length });
|
|
68
|
+
if (tokens.length > 256) throw new Error("check expression exceeds 256 tokens");
|
|
69
|
+
return tokens;
|
|
70
|
+
}
|
|
71
|
+
var Parser = class {
|
|
72
|
+
constructor(tokens, columns) {
|
|
73
|
+
this.tokens = tokens;
|
|
74
|
+
this.columns = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const column of columns) {
|
|
76
|
+
for (const alias of [column.name, column.columnName]) {
|
|
77
|
+
const previous = this.columns.get(alias);
|
|
78
|
+
if (previous && previous.columnName !== column.columnName) {
|
|
79
|
+
throw new Error(`ambiguous check column '${alias}'`);
|
|
80
|
+
}
|
|
81
|
+
this.columns.set(alias, column);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
index = 0;
|
|
86
|
+
depth = 0;
|
|
87
|
+
columns;
|
|
88
|
+
parse() {
|
|
89
|
+
const expression = this.parseOr();
|
|
90
|
+
this.expect("end", "end of expression");
|
|
91
|
+
return expression;
|
|
92
|
+
}
|
|
93
|
+
parseOr() {
|
|
94
|
+
let left = this.parseAnd();
|
|
95
|
+
while (this.matchKeyword("or")) left = `(${left} OR ${this.parseAnd()})`;
|
|
96
|
+
return left;
|
|
97
|
+
}
|
|
98
|
+
parseAnd() {
|
|
99
|
+
let left = this.parseNot();
|
|
100
|
+
while (this.matchKeyword("and")) left = `(${left} AND ${this.parseNot()})`;
|
|
101
|
+
return left;
|
|
102
|
+
}
|
|
103
|
+
parseNot() {
|
|
104
|
+
if (this.matchKeyword("not")) return `(NOT ${this.parseNot()})`;
|
|
105
|
+
return this.parsePrimary();
|
|
106
|
+
}
|
|
107
|
+
parsePrimary() {
|
|
108
|
+
if (this.peek().kind === "lparen") {
|
|
109
|
+
if (++this.depth > 32) throw new Error("check expression nesting exceeds 32 levels");
|
|
110
|
+
this.advance();
|
|
111
|
+
const expression = this.parseOr();
|
|
112
|
+
this.expect("rparen", "')'");
|
|
113
|
+
this.depth--;
|
|
114
|
+
return `(${expression})`;
|
|
115
|
+
}
|
|
116
|
+
return this.parseComparison();
|
|
117
|
+
}
|
|
118
|
+
parseComparison() {
|
|
119
|
+
const left = this.parseValue();
|
|
120
|
+
if (this.matchKeyword("is")) {
|
|
121
|
+
const negate = this.matchKeyword("not");
|
|
122
|
+
this.expectKeyword("null");
|
|
123
|
+
return `${left.sql} IS ${negate ? "NOT " : ""}NULL`;
|
|
124
|
+
}
|
|
125
|
+
if (this.matchKeyword("between")) {
|
|
126
|
+
const lower = this.parseValue();
|
|
127
|
+
this.expectKeyword("and");
|
|
128
|
+
const upper = this.parseValue();
|
|
129
|
+
return `${left.sql} BETWEEN ${lower.sql} AND ${upper.sql}`;
|
|
130
|
+
}
|
|
131
|
+
if (this.matchKeyword("in")) {
|
|
132
|
+
this.expect("lparen", "'(' after IN");
|
|
133
|
+
const values = [this.parseValue()];
|
|
134
|
+
while (this.peek().kind === "comma") {
|
|
135
|
+
this.advance();
|
|
136
|
+
values.push(this.parseValue());
|
|
137
|
+
}
|
|
138
|
+
this.expect("rparen", "')' after IN values");
|
|
139
|
+
if (values.some((value) => !value.literal)) throw new Error("IN values must be literals");
|
|
140
|
+
return `${left.sql} IN (${values.map((value) => value.sql).join(", ")})`;
|
|
141
|
+
}
|
|
142
|
+
const operator = this.expect("operator", "a comparison operator");
|
|
143
|
+
const right = this.parseValue();
|
|
144
|
+
if (operator.value === "~" || operator.value === "!~") {
|
|
145
|
+
if (left.column && left.column.type !== "text") throw new Error("regex checks require a text column");
|
|
146
|
+
if (!right.literal) throw new Error("regex pattern must be a literal");
|
|
147
|
+
}
|
|
148
|
+
return `${left.sql} ${operator.value} ${right.sql}`;
|
|
149
|
+
}
|
|
150
|
+
parseValue() {
|
|
151
|
+
const token = this.advance();
|
|
152
|
+
if (token.kind === "string") return { sql: quoteLiteral(token.value), literal: true };
|
|
153
|
+
if (token.kind === "number") {
|
|
154
|
+
const number = Number(token.value);
|
|
155
|
+
if (!Number.isFinite(number) || Number.isInteger(number) && !Number.isSafeInteger(number)) {
|
|
156
|
+
throw new Error(`unsafe numeric literal at position ${token.position}`);
|
|
157
|
+
}
|
|
158
|
+
return { sql: token.value, literal: true };
|
|
159
|
+
}
|
|
160
|
+
if (token.kind !== "identifier") throw new Error(`expected a column or literal at position ${token.position}`);
|
|
161
|
+
const keyword = token.value.toLowerCase();
|
|
162
|
+
if (keyword === "true" || keyword === "false" || keyword === "null") return { sql: keyword.toUpperCase(), literal: true };
|
|
163
|
+
if (keyword === "btrim") {
|
|
164
|
+
this.expect("lparen", "'(' after btrim");
|
|
165
|
+
const column2 = this.parseColumn();
|
|
166
|
+
this.expect("rparen", "')' after btrim column");
|
|
167
|
+
if (column2.type !== "text") throw new Error("btrim requires a text column");
|
|
168
|
+
return { sql: `btrim(${quoteIdentifier(column2.columnName)})`, column: column2 };
|
|
169
|
+
}
|
|
170
|
+
const column = this.columns.get(token.value);
|
|
171
|
+
if (!column) throw new Error(`unknown check column '${token.value}'`);
|
|
172
|
+
return { sql: quoteIdentifier(column.columnName), column };
|
|
173
|
+
}
|
|
174
|
+
parseColumn() {
|
|
175
|
+
const token = this.expect("identifier", "a column name");
|
|
176
|
+
const column = this.columns.get(token.value);
|
|
177
|
+
if (!column) throw new Error(`unknown check column '${token.value}'`);
|
|
178
|
+
return column;
|
|
179
|
+
}
|
|
180
|
+
peek() {
|
|
181
|
+
return this.tokens[this.index];
|
|
182
|
+
}
|
|
183
|
+
advance() {
|
|
184
|
+
const token = this.peek();
|
|
185
|
+
if (token.kind !== "end") this.index++;
|
|
186
|
+
return token;
|
|
187
|
+
}
|
|
188
|
+
matchKeyword(keyword) {
|
|
189
|
+
if (this.peek().kind !== "identifier" || this.peek().value.toLowerCase() !== keyword) return false;
|
|
190
|
+
this.advance();
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
expectKeyword(keyword) {
|
|
194
|
+
if (!this.matchKeyword(keyword)) throw new Error(`expected '${keyword}' at position ${this.peek().position}`);
|
|
195
|
+
}
|
|
196
|
+
expect(kind, description) {
|
|
197
|
+
const token = this.peek();
|
|
198
|
+
if (token.kind !== kind) throw new Error(`expected ${description} at position ${token.position}`);
|
|
199
|
+
return this.advance();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
function compileCheckExpression(source, columns) {
|
|
203
|
+
return new Parser(tokenize(source), columns).parse();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/constants.ts
|
|
207
|
+
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
208
|
+
var MAX_IDENTIFIER_LENGTH = 63;
|
|
209
|
+
var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
|
|
210
|
+
var CELL_PART_SUFFIX = {
|
|
211
|
+
at: "__at",
|
|
212
|
+
by: "__by",
|
|
213
|
+
rev: "__rev",
|
|
214
|
+
source: "__src",
|
|
215
|
+
conflicts: "__lost"
|
|
216
|
+
};
|
|
217
|
+
var MAX_TRACKED_COLUMN_NAME = MAX_IDENTIFIER_LENGTH - 6;
|
|
218
|
+
var CELLS_FIELD = "cells";
|
|
219
|
+
function cellColumnName(column, part) {
|
|
220
|
+
return `${column}${CELL_PART_SUFFIX[part]}`;
|
|
221
|
+
}
|
|
222
|
+
var MATERIALIZE_ACTOR = "orm:materialize";
|
|
223
|
+
JSON.stringify({ sub: MATERIALIZE_ACTOR, org: null, roles: [], capabilities: [] });
|
|
224
|
+
var policyVariantSchema = zod.z.object({
|
|
225
|
+
/** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
|
|
226
|
+
when: zod.z.string().min(1).max(1e3),
|
|
227
|
+
/** Exactly one of: allow-all bypass, or a row predicate. */
|
|
228
|
+
allow: zod.z.literal(true).optional(),
|
|
229
|
+
policy: zod.z.string().min(1).max(2e3).optional()
|
|
230
|
+
}).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
|
|
231
|
+
message: "A variant declares exactly one of allow: true or policy"
|
|
232
|
+
});
|
|
233
|
+
var policyExpressionSchema = zod.z.union([
|
|
234
|
+
zod.z.string().min(1).max(2e3),
|
|
235
|
+
zod.z.object({
|
|
236
|
+
default: zod.z.string().min(1).max(2e3),
|
|
237
|
+
variants: zod.z.array(policyVariantSchema).max(10).default([])
|
|
238
|
+
})
|
|
239
|
+
]);
|
|
240
|
+
var identifier = zod.z.string().min(1).max(63).regex(/^[a-z][a-z0-9_]*$/);
|
|
241
|
+
var durationMs = zod.z.number().int().positive().max(24 * 60 * 60 * 1e3);
|
|
242
|
+
var RESOLVED_VIEW_SYSTEM_COLUMNS = {
|
|
243
|
+
writebackStatus: { name: "writebackStatus", columnName: "_writeback_status", type: "text" },
|
|
244
|
+
writebackError: { name: "writebackError", columnName: "_writeback_error", type: "text" }
|
|
245
|
+
};
|
|
246
|
+
var RESOLVED_SOURCE_SYSTEM_COLUMNS = {
|
|
247
|
+
tombstoned: { name: "sysTombstoned", columnName: "_tombstoned" },
|
|
248
|
+
fetchedAt: { name: "sysFetchedAt", columnName: "_fetched_at" },
|
|
249
|
+
writebackStatus: { name: "sysWritebackStatus", columnName: "_writeback_status" },
|
|
250
|
+
writebackError: { name: "sysWritebackError", columnName: "_writeback_error" },
|
|
251
|
+
writebackAt: { name: "sysWritebackAt", columnName: "_writeback_at" }
|
|
252
|
+
};
|
|
253
|
+
var RESOLVED_VIEW_CELL_PARTS = ["source", "at", "by", "conflicts"];
|
|
254
|
+
/* @__PURE__ */ new Set([
|
|
255
|
+
...Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName]),
|
|
256
|
+
...Object.values(RESOLVED_SOURCE_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName])
|
|
257
|
+
]);
|
|
258
|
+
var COMPARATORS = ["latest", "highest", "lowest", "priority", "nonNullFirst", "custom"];
|
|
259
|
+
var canonicalFunctionRefSchema = zod.z.object({
|
|
260
|
+
/** `managed` = a Managed Function id/slug; `native` = an in-process Atlas function (reserved). */
|
|
261
|
+
kind: zod.z.enum(["managed", "native"]),
|
|
262
|
+
id: zod.z.string().min(1).max(200)
|
|
263
|
+
}).strict();
|
|
264
|
+
var canonicalFetchShapeSchema = zod.z.union([
|
|
265
|
+
zod.z.object({ shape: zod.z.literal("single") }).strict(),
|
|
266
|
+
zod.z.object({ shape: zod.z.literal("batch"), maxKeys: zod.z.number().int().min(2).max(500) }).strict()
|
|
267
|
+
]);
|
|
268
|
+
var canonicalResolvedSourceSchema = zod.z.object({
|
|
269
|
+
name: identifier,
|
|
270
|
+
kind: zod.z.enum(["manual", "etl", "prefetch"]),
|
|
271
|
+
/** Backing relation in this namespace: an internal table (manual/prefetch) or a synced table (etl). */
|
|
272
|
+
table: identifier,
|
|
273
|
+
/** Resolved columns (logical names) this source supplies — the primary key is implicit. */
|
|
274
|
+
columns: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(500),
|
|
275
|
+
/** etl: logical timestamptz/date column on the synced table used as every cell's updated_at. */
|
|
276
|
+
updatedAt: zod.z.string().min(1).max(63).optional(),
|
|
277
|
+
/** prefetch: resolved column holding the source system's key (may be the primary key). */
|
|
278
|
+
key: zod.z.string().min(1).max(63).optional(),
|
|
279
|
+
fetch: canonicalFetchShapeSchema.optional(),
|
|
280
|
+
function: canonicalFunctionRefSchema.optional(),
|
|
281
|
+
prefetchDefaults: zod.z.object({
|
|
282
|
+
maxStalenessMs: durationMs,
|
|
283
|
+
timeoutMs: durationMs,
|
|
284
|
+
onError: zod.z.enum(["fallback", "fail"])
|
|
285
|
+
}).strict().optional(),
|
|
286
|
+
writeback: zod.z.object({
|
|
287
|
+
columns: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(500),
|
|
288
|
+
defaultMode: zod.z.enum(["sync", "async"]),
|
|
289
|
+
timeoutMs: durationMs,
|
|
290
|
+
/** Remove confirmed cells from the manual source so a stale edit cannot shadow the source. */
|
|
291
|
+
clearManualOnConfirm: zod.z.boolean()
|
|
292
|
+
}).strict().optional()
|
|
293
|
+
}).strict();
|
|
294
|
+
var canonicalResolveRuleSchema = zod.z.object({
|
|
295
|
+
column: zod.z.string().min(1).max(63),
|
|
296
|
+
comparator: zod.z.enum(COMPARATORS),
|
|
297
|
+
/** Source names, in tie-break order. */
|
|
298
|
+
candidates: zod.z.array(identifier).min(1).max(16),
|
|
299
|
+
/** custom only: SQL ORDER BY key over value / updated_at / source / actor / ord. */
|
|
300
|
+
orderBy: zod.z.string().min(1).max(2e3).optional()
|
|
301
|
+
}).strict();
|
|
302
|
+
var canonicalResolvedSchema = zod.z.object({
|
|
303
|
+
sources: zod.z.array(canonicalResolvedSourceSchema).min(1).max(16),
|
|
304
|
+
/** One rule per resolved (non-pk) column; documents the generated view for tooling. */
|
|
305
|
+
resolve: zod.z.array(canonicalResolveRuleSchema).max(500),
|
|
306
|
+
/** Sources whose tombstone hides the resolved row (v1: the manual source only). */
|
|
307
|
+
deletedBy: zod.z.array(identifier).max(1).default([]),
|
|
308
|
+
/**
|
|
309
|
+
* Materialized: the view reads an indexed internal table (`table`) that the
|
|
310
|
+
* data plane keeps in step with `compute`, the internal view holding the
|
|
311
|
+
* resolution query. Omitted when the view computes on read.
|
|
312
|
+
*/
|
|
313
|
+
materialized: zod.z.object({ table: identifier, compute: identifier }).strict().optional(),
|
|
314
|
+
/** Write policies evaluated against the RESOLVED row (reads use the view's read policy). */
|
|
315
|
+
policies: zod.z.object({
|
|
316
|
+
write: policyExpressionSchema.optional(),
|
|
317
|
+
insert: policyExpressionSchema.optional(),
|
|
318
|
+
update: policyExpressionSchema.optional()
|
|
319
|
+
}).strict().default({})
|
|
320
|
+
}).strict();
|
|
321
|
+
function validateResolvedView({ view, tables, views = [] }) {
|
|
322
|
+
const resolved = view.resolved;
|
|
323
|
+
if (!resolved) return [];
|
|
324
|
+
const errors = [];
|
|
325
|
+
const fail = (message) => errors.push(`resolved table '${view.view}': ${message}`);
|
|
326
|
+
const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
|
|
327
|
+
if (!pkColumn) {
|
|
328
|
+
fail("requires exactly one primary-key column");
|
|
329
|
+
return errors;
|
|
330
|
+
}
|
|
331
|
+
const systemNames = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((c) => c.name));
|
|
332
|
+
const scope = {
|
|
333
|
+
view,
|
|
334
|
+
resolved,
|
|
335
|
+
pkColumn,
|
|
336
|
+
dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
|
|
337
|
+
sourceByName: /* @__PURE__ */ new Map(),
|
|
338
|
+
fail
|
|
339
|
+
};
|
|
340
|
+
if (resolved.sources.filter((source) => source.kind === "manual").length !== 1) fail("requires exactly one manual source");
|
|
341
|
+
const tableByName = new Map(tables.map((table) => [table.table, table]));
|
|
342
|
+
for (const source of resolved.sources) {
|
|
343
|
+
if (scope.sourceByName.has(source.name)) fail(`duplicate source '${source.name}'`);
|
|
344
|
+
scope.sourceByName.set(source.name, source);
|
|
345
|
+
const table = tableByName.get(source.table);
|
|
346
|
+
if (!table) fail(`source '${source.name}' references unknown table '${source.table}'`);
|
|
347
|
+
else validateSource({ scope, source, table });
|
|
348
|
+
}
|
|
349
|
+
validateRules(scope);
|
|
350
|
+
validateViewCells(scope);
|
|
351
|
+
if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
|
|
352
|
+
for (const name of resolved.deletedBy) {
|
|
353
|
+
if (scope.sourceByName.get(name)?.kind !== "manual") fail(`deletedBy '${name}' must be the manual source`);
|
|
354
|
+
}
|
|
355
|
+
return errors;
|
|
356
|
+
}
|
|
357
|
+
function validateSource({ scope, source, table }) {
|
|
358
|
+
const { fail } = scope;
|
|
359
|
+
if (source.kind === "etl" && table.source === void 0) fail(`etl source '${source.name}' must reference a synced table`);
|
|
360
|
+
if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
|
|
361
|
+
fail(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
|
|
362
|
+
}
|
|
363
|
+
const hasRev = table.columns.some((column) => column.cell?.part === "rev");
|
|
364
|
+
if (source.kind === "manual" && !hasRev) fail(`manual source '${source.name}' must carry '__rev' cell columns`);
|
|
365
|
+
if (source.kind === "prefetch" && hasRev) fail(`prefetch source '${source.name}' must not carry '__rev' cell columns`);
|
|
366
|
+
const byName = new Map(table.columns.map((c) => [c.name, c]));
|
|
367
|
+
validateSourceColumns({ scope, source, byName });
|
|
368
|
+
if (source.kind === "etl") {
|
|
369
|
+
const updatedAt = source.updatedAt === void 0 ? void 0 : byName.get(source.updatedAt);
|
|
370
|
+
if (source.updatedAt !== void 0 && updatedAt?.type !== "timestamptz" && updatedAt?.type !== "date") {
|
|
371
|
+
fail(`etl source '${source.name}' updatedAt must name a timestamptz/date column on '${source.table}'`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
validatePrefetchFields({ scope, source });
|
|
375
|
+
}
|
|
376
|
+
function validatePrefetchFields({ scope, source }) {
|
|
377
|
+
const { fail, pkColumn } = scope;
|
|
378
|
+
if (source.kind !== "prefetch") {
|
|
379
|
+
if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
|
|
380
|
+
fail(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
|
|
381
|
+
}
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
|
|
385
|
+
fail(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
|
|
386
|
+
} else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
|
|
387
|
+
fail(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
|
|
388
|
+
}
|
|
389
|
+
for (const column of source.writeback?.columns ?? []) {
|
|
390
|
+
if (!source.columns.includes(column)) fail(`prefetch source '${source.name}' writes back '${column}', which it does not supply`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function validateSourceColumns({ scope, source, byName }) {
|
|
394
|
+
const { fail, pkColumn, dataColumns } = scope;
|
|
395
|
+
for (const column of [pkColumn.name, ...source.columns]) {
|
|
396
|
+
const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
|
|
397
|
+
const theirs = byName.get(column);
|
|
398
|
+
if (!own) fail(`source '${source.name}' supplies unknown column '${column}'`);
|
|
399
|
+
else if (!theirs) fail(`source '${source.name}' table '${source.table}' has no column '${column}'`);
|
|
400
|
+
else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail(`column '${column}' has a different type on source '${source.name}'`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail }) {
|
|
404
|
+
const ruled = /* @__PURE__ */ new Set();
|
|
405
|
+
for (const rule of resolved.resolve) {
|
|
406
|
+
if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
|
|
407
|
+
fail(`resolve rule targets unknown column '${rule.column}'`);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (ruled.has(rule.column)) fail(`column '${rule.column}' has more than one resolve rule`);
|
|
411
|
+
ruled.add(rule.column);
|
|
412
|
+
if (rule.comparator === "custom" !== (rule.orderBy !== void 0)) {
|
|
413
|
+
fail(`column '${rule.column}': orderBy is required for custom comparators and only allowed there`);
|
|
414
|
+
}
|
|
415
|
+
if (new Set(rule.candidates).size !== rule.candidates.length) fail(`column '${rule.column}' repeats a candidate`);
|
|
416
|
+
for (const candidate of rule.candidates) {
|
|
417
|
+
const source = sourceByName.get(candidate);
|
|
418
|
+
if (!source) fail(`column '${rule.column}' names unknown candidate source '${candidate}'`);
|
|
419
|
+
else if (!source.columns.includes(rule.column)) fail(`column '${rule.column}' candidate '${candidate}' does not supply it`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
for (const column of dataColumns.keys()) {
|
|
423
|
+
if (column !== pkColumn.name && !ruled.has(column)) fail(`column '${column}' has no resolve rule`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
var VIEW_CELL_TYPES = {
|
|
427
|
+
source: "text",
|
|
428
|
+
at: "timestamptz",
|
|
429
|
+
by: "text",
|
|
430
|
+
conflicts: "jsonb"
|
|
431
|
+
};
|
|
432
|
+
function validateViewCells({ view, dataColumns, pkColumn, fail }) {
|
|
433
|
+
const expected = /* @__PURE__ */ new Map();
|
|
434
|
+
for (const column of dataColumns.values()) {
|
|
435
|
+
if (column.name === pkColumn.name) continue;
|
|
436
|
+
for (const part of RESOLVED_VIEW_CELL_PARTS) {
|
|
437
|
+
expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
for (const column of view.columns) {
|
|
441
|
+
if (!column.cell) continue;
|
|
442
|
+
const want = expected.get(column.name);
|
|
443
|
+
if (!want || want.part !== column.cell.part || want.columnName !== column.columnName || cellColumnName(column.cell.of, column.cell.part) !== column.name) {
|
|
444
|
+
fail(`unexpected cell column '${column.name}'`);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
|
|
448
|
+
expected.delete(column.name);
|
|
449
|
+
}
|
|
450
|
+
for (const name of expected.keys()) fail(`missing cell column '${name}'`);
|
|
451
|
+
}
|
|
452
|
+
var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
|
|
453
|
+
function validateMaterialized({ scope, tableByName, views }) {
|
|
454
|
+
const { view, resolved, fail } = scope;
|
|
455
|
+
const { table: tableName, compute: computeName } = resolved.materialized;
|
|
456
|
+
const table = tableByName.get(tableName);
|
|
457
|
+
if (!table || table.internal !== true || table.materializes !== view.view) {
|
|
458
|
+
fail(`materialized table '${tableName}' must be an internal table that materializes '${view.view}'`);
|
|
459
|
+
} else {
|
|
460
|
+
if (columnSignature(table.columns) !== columnSignature(view.columns)) fail(`materialized table '${tableName}' must have exactly the view's columns`);
|
|
461
|
+
if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? [])) fail(`materialized table '${tableName}' must share the view's primary key`);
|
|
462
|
+
}
|
|
463
|
+
const compute = views.find((candidate) => candidate.view === computeName);
|
|
464
|
+
if (!compute || compute.internal !== true) fail(`compute view '${computeName}' must be an internal view`);
|
|
465
|
+
else if (columnSignature(compute.columns) !== columnSignature(view.columns)) fail(`compute view '${computeName}' must have exactly the view's columns`);
|
|
466
|
+
for (const source of resolved.sources) {
|
|
467
|
+
if (source.kind !== "etl") continue;
|
|
468
|
+
const read = tableByName.get(source.table)?.policies?.read;
|
|
469
|
+
const unconditional = typeof read === "string" ? read.trim() === "true" : read !== void 0 && read.default.trim() === "true" && read.variants.length === 0;
|
|
470
|
+
if (!unconditional) {
|
|
471
|
+
fail(`etl source '${source.name}' must have read policy 'true' to be materialized (the copy is shared by every caller)`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
6
476
|
// src/canonical/v2.ts
|
|
7
477
|
var CANONICAL_FORMAT = 2;
|
|
8
478
|
var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
|
|
9
|
-
var
|
|
479
|
+
var identifier2 = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
|
|
480
|
+
var columnIdentifier = zod.z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
|
|
10
481
|
var COLUMN_TYPES = [
|
|
11
482
|
"uuid",
|
|
12
483
|
"text",
|
|
@@ -21,59 +492,71 @@ var COLUMN_TYPES = [
|
|
|
21
492
|
];
|
|
22
493
|
var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
|
|
23
494
|
var canonicalEnumSchema = zod.z.object({
|
|
24
|
-
name:
|
|
495
|
+
name: identifier2,
|
|
25
496
|
values: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(100)
|
|
26
497
|
});
|
|
27
498
|
var canonicalColumnSchema = zod.z.object({
|
|
28
499
|
/** Property name in TS (camelCase); columnName is the Postgres name. */
|
|
29
500
|
name: zod.z.string().min(1).max(63),
|
|
30
|
-
columnName:
|
|
31
|
-
type: zod.z.union([columnTypeSchema, zod.z.object({ enum:
|
|
501
|
+
columnName: columnIdentifier,
|
|
502
|
+
type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier2 })]),
|
|
32
503
|
nullable: zod.z.boolean().default(true),
|
|
33
504
|
unique: zod.z.boolean().default(false),
|
|
34
505
|
/** Literal default, or one of the allowlisted SQL functions. */
|
|
35
506
|
default: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]).optional(),
|
|
36
507
|
defaultFn: zod.z.enum(["gen_random_uuid()", "now()"]).optional(),
|
|
508
|
+
/**
|
|
509
|
+
* Stamp this column from the database clock on UPDATE (`now()`). The
|
|
510
|
+
* column stays insertable (defaults fill gaps) but is omitted from
|
|
511
|
+
* SetInput / settable so callers cannot backdate it.
|
|
512
|
+
*/
|
|
513
|
+
onUpdateFn: zod.z.enum(["now()"]).optional(),
|
|
37
514
|
description: zod.z.string().max(500).optional(),
|
|
38
515
|
/** Migration hints — same semantics as v1. */
|
|
39
|
-
renamedFrom:
|
|
516
|
+
renamedFrom: columnIdentifier.optional(),
|
|
40
517
|
cast: zod.z.string().max(200).optional(),
|
|
41
|
-
deprecated: zod.z.boolean().default(false)
|
|
518
|
+
deprecated: zod.z.boolean().default(false),
|
|
519
|
+
/**
|
|
520
|
+
* Marks a system cell-metadata column (see CELL_PARTS in constants): part
|
|
521
|
+
* `part` of the cell of logical column `of`. Synthesized by authoring,
|
|
522
|
+
* never client-writable, absent from GraphQL scalar fields. Omitted on
|
|
523
|
+
* ordinary columns so their hashes are unchanged.
|
|
524
|
+
*/
|
|
525
|
+
cell: zod.z.object({ of: zod.z.string().min(1).max(63), part: zod.z.enum(CELL_PARTS) }).strict().optional()
|
|
526
|
+
}).superRefine((column, ctx) => {
|
|
527
|
+
if (column.onUpdateFn === void 0) return;
|
|
528
|
+
if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
|
|
529
|
+
ctx.addIssue({
|
|
530
|
+
code: "custom",
|
|
531
|
+
path: ["onUpdateFn"],
|
|
532
|
+
message: `onUpdateFn '${column.onUpdateFn}' is only valid on timestamptz/date columns`
|
|
533
|
+
});
|
|
534
|
+
}
|
|
42
535
|
});
|
|
43
536
|
var canonicalLinkSchema = zod.z.object({
|
|
44
537
|
name: zod.z.string().min(1).max(63),
|
|
45
538
|
kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
|
|
46
539
|
/** Target table name (namespace-local). Absent for toPlatformUser. */
|
|
47
|
-
target:
|
|
540
|
+
target: identifier2.optional(),
|
|
48
541
|
/** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
|
|
49
|
-
on:
|
|
542
|
+
on: columnIdentifier,
|
|
50
543
|
/** Referenced column on the target (default: target's PK / platform user `sub`). */
|
|
51
|
-
references:
|
|
544
|
+
references: columnIdentifier.optional(),
|
|
52
545
|
/** enforce: true emits a real FK constraint (same-namespace toOne only). */
|
|
53
546
|
enforce: zod.z.boolean().default(false),
|
|
547
|
+
onDelete: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
|
|
548
|
+
onUpdate: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
|
|
54
549
|
description: zod.z.string().max(500).optional()
|
|
55
550
|
});
|
|
56
551
|
var canonicalIndexSchema = zod.z.object({
|
|
57
|
-
columns: zod.z.array(
|
|
552
|
+
columns: zod.z.array(columnIdentifier).min(1),
|
|
58
553
|
unique: zod.z.boolean().default(false),
|
|
59
|
-
name:
|
|
554
|
+
name: identifier2.optional()
|
|
60
555
|
});
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
]);
|
|
556
|
+
var canonicalCheckSchema = zod.z.object({
|
|
557
|
+
name: identifier2,
|
|
558
|
+
expression: zod.z.string().min(1).max(2e3)
|
|
559
|
+
}).strict();
|
|
77
560
|
var canonicalPoliciesSchema = zod.z.object({
|
|
78
561
|
read: policyExpressionSchema.optional(),
|
|
79
562
|
write: policyExpressionSchema.optional(),
|
|
@@ -104,7 +587,7 @@ function isSingleViewQuerySql(value) {
|
|
|
104
587
|
return state === "normal" && !/\$[a-z_0-9]*\$/i.test(query);
|
|
105
588
|
}
|
|
106
589
|
var canonicalActionSchema = zod.z.object({
|
|
107
|
-
name:
|
|
590
|
+
name: identifier2,
|
|
108
591
|
requires: zod.z.string().max(1e3).optional(),
|
|
109
592
|
params: zod.z.record(zod.z.string(), zod.z.object({
|
|
110
593
|
type: columnTypeSchema,
|
|
@@ -116,9 +599,9 @@ var canonicalActionSchema = zod.z.object({
|
|
|
116
599
|
});
|
|
117
600
|
var canonicalCowMutationSchema = zod.z.object({
|
|
118
601
|
kind: zod.z.enum(["override", "revert"]),
|
|
119
|
-
name:
|
|
602
|
+
name: identifier2,
|
|
120
603
|
/** Synced table (same namespace) the overlay copies from / falls back to. */
|
|
121
|
-
synced:
|
|
604
|
+
synced: identifier2,
|
|
122
605
|
/** Single-column primary key PROPERTY (TS camelCase, like column `name`) shared by overlay and synced. */
|
|
123
606
|
pk: zod.z.string().min(1).max(63),
|
|
124
607
|
/** Override: contract properties copied INSERT…SELECT from synced (pk included). Empty for revert. */
|
|
@@ -145,17 +628,28 @@ var canonicalCowMutationSchema = zod.z.object({
|
|
|
145
628
|
}
|
|
146
629
|
}
|
|
147
630
|
});
|
|
631
|
+
var canonicalForeignKeySchema = zod.z.object({
|
|
632
|
+
name: identifier2,
|
|
633
|
+
columns: zod.z.array(columnIdentifier).min(1).max(8),
|
|
634
|
+
target: identifier2,
|
|
635
|
+
references: zod.z.array(columnIdentifier).min(1).max(8),
|
|
636
|
+
deferrable: zod.z.enum(["initially-immediate", "initially-deferred"]).optional(),
|
|
637
|
+
onDelete: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
|
|
638
|
+
onUpdate: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional()
|
|
639
|
+
}).strict();
|
|
148
640
|
var canonicalTableSchema = zod.z.object({
|
|
149
|
-
table:
|
|
641
|
+
table: identifier2,
|
|
150
642
|
description: zod.z.string().max(2e3).optional(),
|
|
151
643
|
/** Table-level rename hint (new in v2). */
|
|
152
|
-
renamedFrom:
|
|
644
|
+
renamedFrom: identifier2.optional(),
|
|
153
645
|
/** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
|
|
154
646
|
source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
|
|
155
647
|
columns: zod.z.array(canonicalColumnSchema).default([]),
|
|
156
|
-
primaryKey: zod.z.array(
|
|
648
|
+
primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
|
|
157
649
|
links: zod.z.array(canonicalLinkSchema).default([]),
|
|
158
650
|
indexes: zod.z.array(canonicalIndexSchema).default([]),
|
|
651
|
+
/** Omitted by old definitions, preserving their existing content hashes. */
|
|
652
|
+
checks: zod.z.array(canonicalCheckSchema).max(100).optional(),
|
|
159
653
|
policies: canonicalPoliciesSchema.default({}),
|
|
160
654
|
/**
|
|
161
655
|
* v1-conversion carry-over only. v2 has no action surface — the runtime
|
|
@@ -164,26 +658,64 @@ var canonicalTableSchema = zod.z.object({
|
|
|
164
658
|
*/
|
|
165
659
|
actions: zod.z.array(canonicalActionSchema).default([]),
|
|
166
660
|
/** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
|
|
167
|
-
cowMutations: zod.z.array(canonicalCowMutationSchema).optional()
|
|
661
|
+
cowMutations: zod.z.array(canonicalCowMutationSchema).optional(),
|
|
662
|
+
/** Omitted by old definitions, preserving their existing content hashes. */
|
|
663
|
+
foreignKeys: zod.z.array(canonicalForeignKeySchema).max(100).optional(),
|
|
664
|
+
/**
|
|
665
|
+
* Per-cell edit metadata (`cellMetadata: true` in authoring): every tracked
|
|
666
|
+
* column `c` carries typed `c__at` / `c__by` system columns (plus `c__rev`
|
|
667
|
+
* on a resolved table's internal sources), listed explicitly in `columns`
|
|
668
|
+
* with a `cell` marker so DDL and migrations treat them as ordinary columns.
|
|
669
|
+
* Omitted when off so existing definitions hash unchanged.
|
|
670
|
+
*/
|
|
671
|
+
cellMetadata: zod.z.literal(true).optional(),
|
|
672
|
+
/**
|
|
673
|
+
* System-owned backing table of a resolved table (its manual or prefetch
|
|
674
|
+
* source). Internal tables get DDL and IR metadata but no GraphQL surface;
|
|
675
|
+
* only the data plane writes them. Omitted when false.
|
|
676
|
+
*/
|
|
677
|
+
internal: zod.z.literal(true).optional(),
|
|
678
|
+
/**
|
|
679
|
+
* The materialized copy of resolved table `materializes` (internal): the
|
|
680
|
+
* same columns as that view, including its cell parts, maintained by the
|
|
681
|
+
* data plane. Omitted otherwise.
|
|
682
|
+
*/
|
|
683
|
+
materializes: identifier2.optional()
|
|
168
684
|
});
|
|
685
|
+
var MAX_VIEW_QUERY = 1e5;
|
|
686
|
+
var MAX_RESOLVED_VIEW_QUERY = 5e6;
|
|
169
687
|
var canonicalViewSchema = zod.z.object({
|
|
170
|
-
view:
|
|
688
|
+
view: identifier2,
|
|
171
689
|
description: zod.z.string().max(2e3).optional(),
|
|
172
690
|
columns: zod.z.array(canonicalColumnSchema).min(1),
|
|
173
691
|
/** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
|
|
174
|
-
primaryKey: zod.z.array(
|
|
692
|
+
primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
|
|
175
693
|
links: zod.z.array(canonicalLinkSchema).default([]),
|
|
176
694
|
policies: zod.z.object({ read: policyExpressionSchema }).strict(),
|
|
177
|
-
query: zod.z.string().min(1).max(
|
|
695
|
+
query: zod.z.string().min(1).max(MAX_RESOLVED_VIEW_QUERY).refine(isSingleViewQuerySql, {
|
|
178
696
|
message: "view query must be one SELECT/WITH statement without comments or dollar-quoted bodies"
|
|
179
697
|
}),
|
|
180
698
|
/** Namespace-local logical relation names, sorted for deterministic hashing. */
|
|
181
|
-
dependencies: zod.z.array(
|
|
699
|
+
dependencies: zod.z.array(identifier2).max(500),
|
|
700
|
+
/**
|
|
701
|
+
* Present when this view is a resolved table (defineResolvedTable): the
|
|
702
|
+
* multi-source metadata behind its generated query. Omitted otherwise so
|
|
703
|
+
* existing views hash unchanged.
|
|
704
|
+
*/
|
|
705
|
+
resolved: canonicalResolvedSchema.optional(),
|
|
706
|
+
/** System-owned view (a materialized resolved table's compute view): no GraphQL surface. */
|
|
707
|
+
internal: zod.z.literal(true).optional()
|
|
182
708
|
}).superRefine((view, ctx) => {
|
|
709
|
+
if (view.resolved === void 0 && view.query.length > MAX_VIEW_QUERY) {
|
|
710
|
+
ctx.addIssue({ code: "custom", path: ["query"], message: `view query must be at most ${MAX_VIEW_QUERY} characters` });
|
|
711
|
+
}
|
|
183
712
|
for (const [index, column] of view.columns.entries()) {
|
|
184
713
|
if (column.default !== void 0 || column.defaultFn !== void 0) {
|
|
185
714
|
ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare a default` });
|
|
186
715
|
}
|
|
716
|
+
if (column.onUpdateFn !== void 0) {
|
|
717
|
+
ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare onUpdateFn` });
|
|
718
|
+
}
|
|
187
719
|
if (column.unique) {
|
|
188
720
|
ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare unique` });
|
|
189
721
|
}
|
|
@@ -191,15 +723,82 @@ var canonicalViewSchema = zod.z.object({
|
|
|
191
723
|
ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare migration hints` });
|
|
192
724
|
}
|
|
193
725
|
}
|
|
726
|
+
if (view.resolved === void 0 && view.internal !== true) {
|
|
727
|
+
for (const [index, column] of view.columns.entries()) {
|
|
728
|
+
if (column.cell) ctx.addIssue({ code: "custom", path: ["columns", index], message: `only resolved tables carry cell columns ('${column.name}')` });
|
|
729
|
+
}
|
|
730
|
+
}
|
|
194
731
|
for (const [index, link] of view.links.entries()) {
|
|
195
732
|
if (link.enforce) {
|
|
196
733
|
ctx.addIssue({ code: "custom", path: ["links", index, "enforce"], message: `view link '${link.name}' cannot enforce a foreign key` });
|
|
197
734
|
}
|
|
735
|
+
if (link.onDelete !== void 0 || link.onUpdate !== void 0) {
|
|
736
|
+
ctx.addIssue({ code: "custom", path: ["links", index], message: `view link '${link.name}' cannot declare referential actions` });
|
|
737
|
+
}
|
|
198
738
|
}
|
|
199
739
|
});
|
|
740
|
+
var CELL_PART_TYPES = {
|
|
741
|
+
at: "timestamptz",
|
|
742
|
+
by: "text",
|
|
743
|
+
rev: "uuid",
|
|
744
|
+
source: "text",
|
|
745
|
+
conflicts: "jsonb"
|
|
746
|
+
};
|
|
747
|
+
function trackedCellColumns(table) {
|
|
748
|
+
const pk = new Set(table.primaryKey ?? []);
|
|
749
|
+
return table.columns.filter((column) => column.cell === void 0 && !pk.has(column.columnName) && column.onUpdateFn === void 0);
|
|
750
|
+
}
|
|
751
|
+
var SUFFIXES = Object.values(CELL_PART_SUFFIX);
|
|
752
|
+
function validateCellMetadata(table, index, ctx) {
|
|
753
|
+
const fail = (message, path = ["tables", index]) => ctx.addIssue({ code: "custom", path, message });
|
|
754
|
+
const cells = table.columns.flatMap((column, position) => column.cell ? [{ column, position, cell: column.cell }] : []);
|
|
755
|
+
if (table.materializes !== void 0) return;
|
|
756
|
+
if (table.cellMetadata !== true) {
|
|
757
|
+
for (const { position } of cells) fail(`cell-metadata columns require cellMetadata on table '${table.table}'`, ["tables", index, "columns", position]);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (table.source !== void 0) {
|
|
761
|
+
fail(`synced table '${table.table}' cannot track cell metadata`);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const tracked = trackedCellColumns(table);
|
|
765
|
+
const byName = new Map(tracked.map((column) => [column.name, column]));
|
|
766
|
+
const withRev = cells.some(({ cell }) => cell.part === "rev");
|
|
767
|
+
if (withRev && table.internal !== true) fail(`only a resolved table's internal sources carry '__rev' cell columns ('${table.table}')`);
|
|
768
|
+
const seen = /* @__PURE__ */ new Set();
|
|
769
|
+
for (const { column, position, cell } of cells) {
|
|
770
|
+
const path = ["tables", index, "columns", position];
|
|
771
|
+
const owner = byName.get(cell.of);
|
|
772
|
+
if (!owner) {
|
|
773
|
+
fail(`cell column '${column.name}' belongs to '${cell.of}', which is not a tracked column`, path);
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (cell.part !== "at" && cell.part !== "by" && cell.part !== "rev") fail(`a table cell column must be part at/by/rev, not '${cell.part}'`, path);
|
|
777
|
+
if (column.name !== cellColumnName(owner.name, cell.part) || column.columnName !== cellColumnName(owner.columnName, cell.part)) {
|
|
778
|
+
fail(`cell column for '${owner.name}' ${cell.part} must be named '${cellColumnName(owner.name, cell.part)}' / '${cellColumnName(owner.columnName, cell.part)}'`, path);
|
|
779
|
+
}
|
|
780
|
+
if (column.type !== CELL_PART_TYPES[cell.part] || !column.nullable || column.unique || column.default !== void 0 || column.defaultFn !== void 0) {
|
|
781
|
+
fail(`cell column '${column.name}' must be a nullable, default-less ${CELL_PART_TYPES[cell.part]}`, path);
|
|
782
|
+
}
|
|
783
|
+
const key = `${cell.of}:${cell.part}`;
|
|
784
|
+
if (seen.has(key)) fail(`duplicate cell column '${column.name}'`, path);
|
|
785
|
+
seen.add(key);
|
|
786
|
+
}
|
|
787
|
+
for (const column of tracked) {
|
|
788
|
+
if (column.columnName.length > MAX_TRACKED_COLUMN_NAME) {
|
|
789
|
+
fail(`column '${column.columnName}' is too long to track cell metadata (max ${MAX_TRACKED_COLUMN_NAME} characters)`);
|
|
790
|
+
}
|
|
791
|
+
if (SUFFIXES.some((suffix) => column.columnName.endsWith(suffix))) {
|
|
792
|
+
fail(`column '${column.columnName}' ends in a reserved cell-metadata suffix (${SUFFIXES.join(", ")})`);
|
|
793
|
+
}
|
|
794
|
+
for (const part of withRev ? ["at", "by", "rev"] : ["at", "by"]) {
|
|
795
|
+
if (!seen.has(`${column.name}:${part}`)) fail(`tracked column '${column.name}' is missing its '${cellColumnName(column.columnName, part)}' cell column`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
200
799
|
var canonicalDefinitionSchema = zod.z.object({
|
|
201
800
|
definition_format: zod.z.literal(CANONICAL_FORMAT),
|
|
202
|
-
namespace:
|
|
801
|
+
namespace: identifier2.max(41),
|
|
203
802
|
description: zod.z.string().max(2e3).optional(),
|
|
204
803
|
enums: zod.z.array(canonicalEnumSchema).default([]),
|
|
205
804
|
tables: zod.z.array(canonicalTableSchema).min(0).max(500),
|
|
@@ -217,6 +816,74 @@ var canonicalDefinitionSchema = zod.z.object({
|
|
|
217
816
|
ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
|
|
218
817
|
}
|
|
219
818
|
seen.add(table.table);
|
|
819
|
+
validateCellMetadata(table, index, ctx);
|
|
820
|
+
if (table.source !== void 0) {
|
|
821
|
+
for (const [columnIndex, column] of table.columns.entries()) {
|
|
822
|
+
if (column.onUpdateFn !== void 0) {
|
|
823
|
+
ctx.addIssue({
|
|
824
|
+
code: "custom",
|
|
825
|
+
path: ["tables", index, "columns", columnIndex],
|
|
826
|
+
message: `synced table '${table.table}' column '${column.name}' cannot declare onUpdateFn`
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
833
|
+
for (const [tableIndex, table] of definition.tables.entries()) {
|
|
834
|
+
const names = /* @__PURE__ */ new Set([
|
|
835
|
+
...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
|
|
836
|
+
...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
|
|
837
|
+
...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
|
|
838
|
+
]);
|
|
839
|
+
for (const [index, link] of table.links.entries()) {
|
|
840
|
+
if ((link.onDelete !== void 0 || link.onUpdate !== void 0) && (!link.enforce || link.kind !== "toOne")) {
|
|
841
|
+
ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", index], message: "referential actions require an enforced toOne link" });
|
|
842
|
+
}
|
|
843
|
+
if (link.onDelete === "set null" || link.onUpdate === "set null") {
|
|
844
|
+
const source = table.columns.find((column) => column.columnName === link.on);
|
|
845
|
+
if (source?.nullable === false || table.primaryKey?.includes(link.on)) ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", index], message: "SET NULL requires a nullable source column" });
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
for (const [index, check] of (table.checks ?? []).entries()) {
|
|
849
|
+
const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "checks", index], message });
|
|
850
|
+
if (table.source) fail("synced tables cannot declare checks");
|
|
851
|
+
if (names.has(check.name)) fail(`duplicate constraint '${check.name}'`);
|
|
852
|
+
names.add(check.name);
|
|
853
|
+
try {
|
|
854
|
+
compileCheckExpression(check.expression, table.columns);
|
|
855
|
+
} catch (error) {
|
|
856
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
for (const [index, fk] of (table.foreignKeys ?? []).entries()) {
|
|
860
|
+
const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index], message });
|
|
861
|
+
if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
|
|
862
|
+
names.add(fk.name);
|
|
863
|
+
const target = fkTables.get(fk.target);
|
|
864
|
+
if (table.source || !target || target.source) {
|
|
865
|
+
fail("foreign keys must connect owned tables in the same namespace");
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
|
|
869
|
+
if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
|
|
870
|
+
for (const [position, name] of fk.columns.entries()) {
|
|
871
|
+
const sourceColumn = table.columns.find((column) => column.columnName === name);
|
|
872
|
+
const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
|
|
873
|
+
if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
|
|
874
|
+
else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
|
|
875
|
+
}
|
|
876
|
+
if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
|
|
877
|
+
if (fk.columns.some((name) => table.columns.find((column) => column.columnName === name)?.nullable === false || table.primaryKey?.includes(name))) fail("SET NULL requires nullable source columns");
|
|
878
|
+
}
|
|
879
|
+
const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index2) => index2.unique).map((index2) => index2.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
|
|
880
|
+
if (!uniqueKeys.some((key) => key.length === fk.references.length && key.every((column) => fk.references.includes(column)))) fail("foreign key target columns must form a unique key");
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
for (const [viewIndex, view] of (definition.views ?? []).entries()) {
|
|
884
|
+
for (const message of validateResolvedView({ view, tables: definition.tables, views: definition.views ?? [] })) {
|
|
885
|
+
ctx.addIssue({ code: "custom", path: ["views", viewIndex, "resolved"], message });
|
|
886
|
+
}
|
|
220
887
|
}
|
|
221
888
|
const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
|
|
222
889
|
for (const [tableIndex, table] of definition.tables.entries()) {
|
|
@@ -341,8 +1008,12 @@ function canonicalRelations(definition) {
|
|
|
341
1008
|
...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
|
|
342
1009
|
links: view.links,
|
|
343
1010
|
indexes: [],
|
|
344
|
-
|
|
345
|
-
|
|
1011
|
+
// A resolved table's write policies (evaluated against the resolved row)
|
|
1012
|
+
// ride alongside the view's read policy.
|
|
1013
|
+
policies: { read: view.policies.read, ...view.resolved?.policies ?? {} },
|
|
1014
|
+
actions: [],
|
|
1015
|
+
...view.resolved !== void 0 ? { resolved: view.resolved } : {},
|
|
1016
|
+
...view.internal ? { internal: true } : {}
|
|
346
1017
|
}));
|
|
347
1018
|
return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
|
|
348
1019
|
}
|
|
@@ -352,12 +1023,14 @@ function normalizeCanonical(definition) {
|
|
|
352
1023
|
...definition,
|
|
353
1024
|
enums: sortByName(definition.enums),
|
|
354
1025
|
tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t) => {
|
|
355
|
-
const { cowMutations, ...rest } = t;
|
|
1026
|
+
const { cowMutations, foreignKeys, checks, ...rest } = t;
|
|
356
1027
|
return {
|
|
357
1028
|
...rest,
|
|
358
1029
|
links: sortByName(t.links),
|
|
359
1030
|
indexes: [...t.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
|
|
360
1031
|
actions: sortByName(t.actions),
|
|
1032
|
+
...foreignKeys?.length ? { foreignKeys: sortByName(foreignKeys) } : {},
|
|
1033
|
+
...checks?.length ? { checks: sortByName(checks) } : {},
|
|
361
1034
|
// Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
|
|
362
1035
|
...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
|
|
363
1036
|
};
|
|
@@ -446,6 +1119,8 @@ var ORDER_BY_ENUM_VALUES = [
|
|
|
446
1119
|
"DESC_NULLS_LAST"
|
|
447
1120
|
];
|
|
448
1121
|
var PLATFORM_USER_TYPENAME = "PlatformUser";
|
|
1122
|
+
var DATE_BUCKET_ENUM = "DateBucket";
|
|
1123
|
+
var BUCKETABLE_COLUMN_TYPES = ["timestamptz", "date"];
|
|
449
1124
|
function tableNames(table) {
|
|
450
1125
|
const root = camelCase(table);
|
|
451
1126
|
const rootPascal = pascalCase(table);
|
|
@@ -461,6 +1136,7 @@ function tableNames(table) {
|
|
|
461
1136
|
updateByPkRoot: `update${rootPascal}ByPk`,
|
|
462
1137
|
deleteRoot: `delete${rootPascal}`,
|
|
463
1138
|
deleteByPkRoot: `delete${rootPascal}ByPk`,
|
|
1139
|
+
revertByPkRoot: `revert${rootPascal}ByPk`,
|
|
464
1140
|
boolExp: `${type}BoolExp`,
|
|
465
1141
|
orderBy: `${type}OrderBy`,
|
|
466
1142
|
selectColumn: `${type}SelectColumn`,
|
|
@@ -478,9 +1154,426 @@ function tableNames(table) {
|
|
|
478
1154
|
sumFields: `${type}SumFields`,
|
|
479
1155
|
avgFields: `${type}AvgFields`,
|
|
480
1156
|
aggregateBoolExp: `${type}AggregateBoolExp`,
|
|
481
|
-
aggregateBoolExpCount: `${type}AggregateBoolExpCount
|
|
1157
|
+
aggregateBoolExpCount: `${type}AggregateBoolExpCount`,
|
|
1158
|
+
aggregateGroupsRoot: `${root}AggregateGroups`,
|
|
1159
|
+
groupByColumnEnum: `${type}GroupByColumn`,
|
|
1160
|
+
groupByInput: `${type}GroupBy`,
|
|
1161
|
+
groupKey: `${type}GroupKey`,
|
|
1162
|
+
aggregateGroup: `${type}AggregateGroup`
|
|
482
1163
|
};
|
|
483
1164
|
}
|
|
1165
|
+
var CELL_TYPENAMES = {
|
|
1166
|
+
/** A plain table's cell: when and by whom it was last written. */
|
|
1167
|
+
stamp: "CellStamp",
|
|
1168
|
+
/** A resolved table's cell: which source won, when, by whom, and newer losers. */
|
|
1169
|
+
resolved: "ResolvedCell",
|
|
1170
|
+
/** A newer candidate that lost to the column's rule. */
|
|
1171
|
+
conflict: "CellConflict"
|
|
1172
|
+
};
|
|
1173
|
+
function cellsTypename(typename) {
|
|
1174
|
+
return `${typename}Cells`;
|
|
1175
|
+
}
|
|
1176
|
+
var IR_VERSION = 2;
|
|
1177
|
+
var LEGACY_IR_VERSION = 1;
|
|
1178
|
+
var irVersionSchema = zod.z.union([zod.z.literal(LEGACY_IR_VERSION), zod.z.literal(IR_VERSION)]);
|
|
1179
|
+
zod.z.object({ irVersion: irVersionSchema });
|
|
1180
|
+
var scalarLiteral = zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]);
|
|
1181
|
+
var valueIrSchema = zod.z.discriminatedUnion("kind", [
|
|
1182
|
+
zod.z.object({ kind: zod.z.literal("literal"), value: zod.z.union([scalarLiteral, zod.z.array(scalarLiteral)]) }),
|
|
1183
|
+
zod.z.object({ kind: zod.z.literal("variable"), name: zod.z.string() }),
|
|
1184
|
+
zod.z.object({ kind: zod.z.literal("claim"), key: zod.z.enum(["sub", "org"]) })
|
|
1185
|
+
]);
|
|
1186
|
+
var CMP_OPS = [
|
|
1187
|
+
"eq",
|
|
1188
|
+
"neq",
|
|
1189
|
+
"gt",
|
|
1190
|
+
"gte",
|
|
1191
|
+
"lt",
|
|
1192
|
+
"lte",
|
|
1193
|
+
"in",
|
|
1194
|
+
"nin",
|
|
1195
|
+
"like",
|
|
1196
|
+
"ilike",
|
|
1197
|
+
"isNull",
|
|
1198
|
+
"contains",
|
|
1199
|
+
"hasKey"
|
|
1200
|
+
];
|
|
1201
|
+
var predIrSchema = zod.z.lazy(
|
|
1202
|
+
() => zod.z.discriminatedUnion("kind", [
|
|
1203
|
+
zod.z.object({ kind: zod.z.literal("and"), operands: zod.z.array(predIrSchema).min(1) }),
|
|
1204
|
+
zod.z.object({ kind: zod.z.literal("or"), operands: zod.z.array(predIrSchema).min(1) }),
|
|
1205
|
+
zod.z.object({ kind: zod.z.literal("not"), operand: predIrSchema }),
|
|
1206
|
+
zod.z.object({ kind: zod.z.literal("cmp"), op: zod.z.enum(CMP_OPS), column: zod.z.string(), value: valueIrSchema }),
|
|
1207
|
+
zod.z.object({ kind: zod.z.literal("exists"), link: zod.z.string(), where: predIrSchema }),
|
|
1208
|
+
zod.z.object({
|
|
1209
|
+
kind: zod.z.literal("aggPredicate"),
|
|
1210
|
+
link: zod.z.string(),
|
|
1211
|
+
fn: zod.z.literal("count"),
|
|
1212
|
+
op: zod.z.enum(["eq", "neq", "gt", "gte", "lt", "lte"]),
|
|
1213
|
+
value: zod.z.number(),
|
|
1214
|
+
where: predIrSchema.optional()
|
|
1215
|
+
}),
|
|
1216
|
+
zod.z.object({ kind: zod.z.literal("capability"), key: zod.z.string() }),
|
|
1217
|
+
zod.z.object({ kind: zod.z.literal("role"), key: zod.z.string() }),
|
|
1218
|
+
zod.z.object({ kind: zod.z.literal("dynamicFilter"), slot: zod.z.string() }),
|
|
1219
|
+
zod.z.object({ kind: zod.z.literal("const"), value: zod.z.boolean() })
|
|
1220
|
+
])
|
|
1221
|
+
);
|
|
1222
|
+
var orderIrSchema = zod.z.discriminatedUnion("kind", [
|
|
1223
|
+
zod.z.object({
|
|
1224
|
+
kind: zod.z.literal("column"),
|
|
1225
|
+
column: zod.z.string(),
|
|
1226
|
+
/** Order through a to-one link: `link` is set and `column` is on the target. */
|
|
1227
|
+
link: zod.z.string().optional(),
|
|
1228
|
+
direction: zod.z.enum(["asc", "desc"]),
|
|
1229
|
+
nulls: zod.z.enum(["first", "last"]).optional()
|
|
1230
|
+
}),
|
|
1231
|
+
/** §4f: `$orderBy: [FooOrderBy!]` variable — validated + lowered at runtime. */
|
|
1232
|
+
zod.z.object({ kind: zod.z.literal("dynamic"), slot: zod.z.string() })
|
|
1233
|
+
]);
|
|
1234
|
+
var limitIrSchema = zod.z.object({
|
|
1235
|
+
/** Fixed value or a variable, clamped to [1, max]; absent → default. */
|
|
1236
|
+
variable: zod.z.string().optional(),
|
|
1237
|
+
value: zod.z.number().int().positive().optional(),
|
|
1238
|
+
default: zod.z.number().int().positive(),
|
|
1239
|
+
max: zod.z.number().int().positive()
|
|
1240
|
+
});
|
|
1241
|
+
var selectionIrSchema = zod.z.lazy(
|
|
1242
|
+
() => zod.z.object({
|
|
1243
|
+
typename: zod.z.string(),
|
|
1244
|
+
columns: zod.z.array(zod.z.object({ alias: zod.z.string(), column: zod.z.string() })),
|
|
1245
|
+
cells: zod.z.object({
|
|
1246
|
+
alias: zod.z.string(),
|
|
1247
|
+
typename: zod.z.string(),
|
|
1248
|
+
fields: zod.z.array(zod.z.object({
|
|
1249
|
+
alias: zod.z.string(),
|
|
1250
|
+
column: zod.z.string(),
|
|
1251
|
+
typename: zod.z.string(),
|
|
1252
|
+
parts: zod.z.array(zod.z.object({
|
|
1253
|
+
alias: zod.z.string(),
|
|
1254
|
+
part: zod.z.enum(["source", "at", "by", "conflicts"]),
|
|
1255
|
+
fields: zod.z.array(zod.z.object({ alias: zod.z.string(), field: zod.z.enum(["source", "at", "value"]) })).optional(),
|
|
1256
|
+
typename: zod.z.string().optional()
|
|
1257
|
+
}))
|
|
1258
|
+
}))
|
|
1259
|
+
}).optional(),
|
|
1260
|
+
relations: zod.z.array(zod.z.object({
|
|
1261
|
+
alias: zod.z.string(),
|
|
1262
|
+
link: zod.z.string(),
|
|
1263
|
+
selection: selectionIrSchema,
|
|
1264
|
+
where: predIrSchema.optional(),
|
|
1265
|
+
orderBy: zod.z.array(orderIrSchema).optional(),
|
|
1266
|
+
limit: limitIrSchema.optional(),
|
|
1267
|
+
offset: zod.z.object({ variable: zod.z.string().optional(), value: zod.z.number().int().optional() }).optional()
|
|
1268
|
+
})),
|
|
1269
|
+
aggregates: zod.z.array(zod.z.object({
|
|
1270
|
+
alias: zod.z.string(),
|
|
1271
|
+
link: zod.z.string().optional(),
|
|
1272
|
+
where: predIrSchema.optional(),
|
|
1273
|
+
count: zod.z.object({ columns: zod.z.array(zod.z.string()).optional(), distinct: zod.z.boolean().optional() }).optional(),
|
|
1274
|
+
sum: zod.z.array(zod.z.string()).optional(),
|
|
1275
|
+
avg: zod.z.array(zod.z.string()).optional(),
|
|
1276
|
+
nodes: selectionIrSchema.optional()
|
|
1277
|
+
}))
|
|
1278
|
+
})
|
|
1279
|
+
);
|
|
1280
|
+
var includeIfSchema = zod.z.object({ variable: zod.z.string(), negated: zod.z.boolean() });
|
|
1281
|
+
var prefetchPlanIrSchema = zod.z.object({
|
|
1282
|
+
source: zod.z.string(),
|
|
1283
|
+
maxStalenessMs: zod.z.number().int().positive().optional(),
|
|
1284
|
+
key: zod.z.union([zod.z.object({ one: valueIrSchema }), zod.z.object({ many: valueIrSchema })])
|
|
1285
|
+
});
|
|
1286
|
+
var selectRootSchema = zod.z.object({
|
|
1287
|
+
kind: zod.z.literal("select"),
|
|
1288
|
+
alias: zod.z.string(),
|
|
1289
|
+
table: zod.z.string(),
|
|
1290
|
+
selection: selectionIrSchema,
|
|
1291
|
+
/** Composed at compile time: AND(policy, documentWhere?, dynamicFilter?). */
|
|
1292
|
+
where: predIrSchema,
|
|
1293
|
+
orderBy: zod.z.array(orderIrSchema).default([]),
|
|
1294
|
+
distinctOn: zod.z.union([zod.z.array(zod.z.string()), zod.z.object({ slot: zod.z.string() })]).optional(),
|
|
1295
|
+
limit: limitIrSchema,
|
|
1296
|
+
offset: zod.z.object({ variable: zod.z.string().optional(), value: zod.z.number().int().optional() }).optional(),
|
|
1297
|
+
includeIf: includeIfSchema.optional(),
|
|
1298
|
+
prefetch: zod.z.array(prefetchPlanIrSchema).optional()
|
|
1299
|
+
});
|
|
1300
|
+
var byPkRootSchema = zod.z.object({
|
|
1301
|
+
kind: zod.z.literal("byPk"),
|
|
1302
|
+
alias: zod.z.string(),
|
|
1303
|
+
table: zod.z.string(),
|
|
1304
|
+
selection: selectionIrSchema,
|
|
1305
|
+
pk: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })).min(1),
|
|
1306
|
+
/** Policy predicate — ANDed with the PK match. */
|
|
1307
|
+
where: predIrSchema,
|
|
1308
|
+
includeIf: includeIfSchema.optional(),
|
|
1309
|
+
prefetch: zod.z.array(prefetchPlanIrSchema).optional()
|
|
1310
|
+
});
|
|
1311
|
+
var aggregateRootSchema = zod.z.object({
|
|
1312
|
+
kind: zod.z.literal("aggregate"),
|
|
1313
|
+
alias: zod.z.string(),
|
|
1314
|
+
table: zod.z.string(),
|
|
1315
|
+
where: predIrSchema,
|
|
1316
|
+
count: zod.z.object({ columns: zod.z.array(zod.z.string()).optional(), distinct: zod.z.boolean().optional() }).optional(),
|
|
1317
|
+
sum: zod.z.array(zod.z.string()).optional(),
|
|
1318
|
+
avg: zod.z.array(zod.z.string()).optional(),
|
|
1319
|
+
nodes: selectionIrSchema.optional(),
|
|
1320
|
+
includeIf: includeIfSchema.optional()
|
|
1321
|
+
});
|
|
1322
|
+
var DATE_BUCKETS = ["year", "quarter", "month", "week", "day", "hour"];
|
|
1323
|
+
var groupByKeyIrSchema = zod.z.object({
|
|
1324
|
+
column: zod.z.string(),
|
|
1325
|
+
bucket: zod.z.enum(DATE_BUCKETS).optional()
|
|
1326
|
+
});
|
|
1327
|
+
var aggregateGroupsRootSchema = zod.z.object({
|
|
1328
|
+
kind: zod.z.literal("aggregateGroups"),
|
|
1329
|
+
alias: zod.z.string(),
|
|
1330
|
+
table: zod.z.string(),
|
|
1331
|
+
/** Composed at compile time: AND(policy, documentWhere?, dynamicFilter?). */
|
|
1332
|
+
where: predIrSchema,
|
|
1333
|
+
groupBy: zod.z.array(groupByKeyIrSchema).min(1),
|
|
1334
|
+
/**
|
|
1335
|
+
* The `key { … }` projection: which group columns appear in each entry's key
|
|
1336
|
+
* object, in selection order. A subset of `groupBy` (the compiler rejects a
|
|
1337
|
+
* key field that is not a group key — it would be indeterminate within the
|
|
1338
|
+
* group). Empty when the document selects only `aggregate`.
|
|
1339
|
+
*/
|
|
1340
|
+
key: zod.z.array(zod.z.object({ alias: zod.z.string(), column: zod.z.string() })).default([]),
|
|
1341
|
+
count: zod.z.object({ columns: zod.z.array(zod.z.string()).optional(), distinct: zod.z.boolean().optional() }).optional(),
|
|
1342
|
+
sum: zod.z.array(zod.z.string()).optional(),
|
|
1343
|
+
avg: zod.z.array(zod.z.string()).optional(),
|
|
1344
|
+
/** Bounds the GROUP count, not the scanned rows — see MAX_GROUPS. */
|
|
1345
|
+
limit: limitIrSchema,
|
|
1346
|
+
offset: zod.z.object({ variable: zod.z.string().optional(), value: zod.z.number().int().optional() }).optional(),
|
|
1347
|
+
includeIf: includeIfSchema.optional()
|
|
1348
|
+
});
|
|
1349
|
+
var writeValuesSchema = zod.z.union([
|
|
1350
|
+
zod.z.object({ slot: zod.z.string() }),
|
|
1351
|
+
zod.z.object({ entries: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })) })
|
|
1352
|
+
]);
|
|
1353
|
+
var onConflictIrSchema = zod.z.union([
|
|
1354
|
+
zod.z.object({ slot: zod.z.string() }),
|
|
1355
|
+
zod.z.object({ constraint: zod.z.string(), updateColumns: zod.z.array(zod.z.string()) })
|
|
1356
|
+
]);
|
|
1357
|
+
var mutationRootSchema = zod.z.object({
|
|
1358
|
+
kind: zod.z.literal("mutation"),
|
|
1359
|
+
/**
|
|
1360
|
+
* CRUD verbs plus the structured copy-on-write ops. `cowOverride` copies the
|
|
1361
|
+
* synced row into the overlay (INSERT…SELECT, ON CONFLICT DO NOTHING) then
|
|
1362
|
+
* applies `set` — both statements generated by the executor from this
|
|
1363
|
+
* metadata, never authored SQL. `cowRevert` deletes the override so the
|
|
1364
|
+
* coalesce view falls back to synced. Both are gated on a matching synced
|
|
1365
|
+
* row and carry the write policy in `where` like update/delete.
|
|
1366
|
+
*/
|
|
1367
|
+
op: zod.z.enum(["insert", "insertOne", "update", "updateByPk", "delete", "deleteByPk", "cowOverride", "cowRevert", "resolvedWrite"]),
|
|
1368
|
+
/**
|
|
1369
|
+
* resolvedWrite: a write to a resolved table (`table` is its view). The
|
|
1370
|
+
* executor gates on the resolved row (write policy in `where`), writes only
|
|
1371
|
+
* the touched cells into the manual source, then re-checks `insertCheck`
|
|
1372
|
+
* against the resolved row. `revert` clears `revertColumns` from the manual
|
|
1373
|
+
* source; `delete` tombstones the row there.
|
|
1374
|
+
*/
|
|
1375
|
+
resolvedAction: zod.z.enum(["insert", "update", "delete", "revert"]).optional(),
|
|
1376
|
+
revertColumns: zod.z.array(zod.z.string()).optional(),
|
|
1377
|
+
/** `@writeback(source:, mode:)` directives on this root. */
|
|
1378
|
+
writeback: zod.z.array(zod.z.object({ source: zod.z.string(), mode: zod.z.enum(["sync", "async"]) })).optional(),
|
|
1379
|
+
/** `@localOnly(source:)` — writeback columns deliberately kept local for these sources. */
|
|
1380
|
+
localOnly: zod.z.array(zod.z.string()).optional(),
|
|
1381
|
+
alias: zod.z.string(),
|
|
1382
|
+
table: zod.z.string(),
|
|
1383
|
+
/** CoW ops: logical name of the synced twin (read through its policy-filtered view). */
|
|
1384
|
+
cowSynced: zod.z.string().optional(),
|
|
1385
|
+
/** cowOverride: logical properties copied INSERT…SELECT from synced (pk included). */
|
|
1386
|
+
cowCopyColumns: zod.z.array(zod.z.string()).optional(),
|
|
1387
|
+
/** cowOverride: insert-policy WITH CHECK evaluated over the copied rows. */
|
|
1388
|
+
cowCopyCheck: predIrSchema.optional(),
|
|
1389
|
+
/** insert/insertOne: rows come from a slot ($object/$objects) or literals. */
|
|
1390
|
+
objects: zod.z.union([
|
|
1391
|
+
zod.z.object({ slot: zod.z.string() }),
|
|
1392
|
+
zod.z.object({ rows: zod.z.array(zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema }))) })
|
|
1393
|
+
]).optional(),
|
|
1394
|
+
/** Server-pinned columns (e.g. author_sub ← claim('sub')) merged into every inserted row. */
|
|
1395
|
+
pinnedColumns: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })).default([]),
|
|
1396
|
+
onConflict: onConflictIrSchema.optional(),
|
|
1397
|
+
set: writeValuesSchema.optional(),
|
|
1398
|
+
append: writeValuesSchema.optional(),
|
|
1399
|
+
inc: writeValuesSchema.optional(),
|
|
1400
|
+
/** update/delete row filter: AND(writePolicy, documentWhere/pk, dynamicFilter?). */
|
|
1401
|
+
where: predIrSchema.optional(),
|
|
1402
|
+
pk: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })).optional(),
|
|
1403
|
+
/** WITH CHECK analogue evaluated against NEW row values (insert/update). */
|
|
1404
|
+
insertCheck: predIrSchema.optional(),
|
|
1405
|
+
returning: selectionIrSchema.nullable(),
|
|
1406
|
+
affectedRows: zod.z.boolean().default(false),
|
|
1407
|
+
/** v2: abort the transaction when the actual write count differs, regardless of selection. */
|
|
1408
|
+
assertAffectedRows: zod.z.number().int().min(0).max(2147483647).optional(),
|
|
1409
|
+
includeIf: includeIfSchema.optional()
|
|
1410
|
+
});
|
|
1411
|
+
var rootIrSchema = zod.z.discriminatedUnion("kind", [
|
|
1412
|
+
selectRootSchema,
|
|
1413
|
+
byPkRootSchema,
|
|
1414
|
+
aggregateRootSchema,
|
|
1415
|
+
aggregateGroupsRootSchema,
|
|
1416
|
+
mutationRootSchema
|
|
1417
|
+
]);
|
|
1418
|
+
var INPUT_REF_KINDS = [
|
|
1419
|
+
"boolExp",
|
|
1420
|
+
"orderBy",
|
|
1421
|
+
"selectColumn",
|
|
1422
|
+
"insertInput",
|
|
1423
|
+
"setInput",
|
|
1424
|
+
"appendInput",
|
|
1425
|
+
"onConflict"
|
|
1426
|
+
];
|
|
1427
|
+
var variableDefaultIrSchema = zod.z.object({
|
|
1428
|
+
value: zod.z.union([scalarLiteral, zod.z.array(scalarLiteral)])
|
|
1429
|
+
});
|
|
1430
|
+
var variableIrSchema = zod.z.object({
|
|
1431
|
+
name: zod.z.string(),
|
|
1432
|
+
type: zod.z.union([
|
|
1433
|
+
zod.z.object({ kind: zod.z.literal("scalar"), scalar: zod.z.enum(COLUMN_TYPES) }),
|
|
1434
|
+
/** `values` is the declared membership whitelist — the executor validates against it. */
|
|
1435
|
+
zod.z.object({ kind: zod.z.literal("enum"), name: zod.z.string(), values: zod.z.array(zod.z.string()).min(1) }),
|
|
1436
|
+
/** §4f dynamic slot: validated against `tables[table]` whitelists at runtime. */
|
|
1437
|
+
zod.z.object({ kind: zod.z.literal("inputRef"), input: zod.z.enum(INPUT_REF_KINDS), table: zod.z.string() })
|
|
1438
|
+
]),
|
|
1439
|
+
nullable: zod.z.boolean(),
|
|
1440
|
+
list: zod.z.boolean(),
|
|
1441
|
+
/** Absent = no default declared; see {@link variableDefaultIrSchema}. */
|
|
1442
|
+
defaultValue: variableDefaultIrSchema.optional()
|
|
1443
|
+
});
|
|
1444
|
+
var compiledOperationSchema = zod.z.object({
|
|
1445
|
+
irVersion: irVersionSchema,
|
|
1446
|
+
name: zod.z.string(),
|
|
1447
|
+
opHash: zod.z.string().regex(/^sha256:[0-9a-f]{64}$/),
|
|
1448
|
+
kind: zod.z.enum(["query", "mutation"]),
|
|
1449
|
+
requires: zod.z.object({
|
|
1450
|
+
capability: zod.z.string().optional(),
|
|
1451
|
+
role: zod.z.string().optional()
|
|
1452
|
+
}).nullable(),
|
|
1453
|
+
variables: zod.z.array(variableIrSchema),
|
|
1454
|
+
/** Every table the op touches — data-token scoping + audit. */
|
|
1455
|
+
tableSet: zod.z.array(zod.z.string()).min(1),
|
|
1456
|
+
roots: zod.z.array(rootIrSchema).min(1)
|
|
1457
|
+
}).superRefine((operation, ctx) => {
|
|
1458
|
+
for (const [index, root] of operation.roots.entries()) {
|
|
1459
|
+
if (root.kind !== "mutation" || root.assertAffectedRows === void 0) continue;
|
|
1460
|
+
if (operation.irVersion < 2 || operation.kind !== "mutation" || root.includeIf !== void 0) {
|
|
1461
|
+
ctx.addIssue({ code: "custom", path: ["roots", index], message: "affected-row assertions require unconditional mutation roots and IR v2" });
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
var resolvedSourceIrSchema = zod.z.object({
|
|
1466
|
+
name: zod.z.string(),
|
|
1467
|
+
kind: zod.z.enum(["manual", "etl", "prefetch"]),
|
|
1468
|
+
/** Logical table key of the backing relation. */
|
|
1469
|
+
table: zod.z.string(),
|
|
1470
|
+
columns: zod.z.array(zod.z.string()),
|
|
1471
|
+
key: zod.z.string().optional(),
|
|
1472
|
+
fetch: zod.z.union([
|
|
1473
|
+
zod.z.object({ shape: zod.z.literal("single") }),
|
|
1474
|
+
zod.z.object({ shape: zod.z.literal("batch"), maxKeys: zod.z.number().int().positive() })
|
|
1475
|
+
]).optional(),
|
|
1476
|
+
function: zod.z.object({ kind: zod.z.enum(["managed", "native"]), id: zod.z.string() }).optional(),
|
|
1477
|
+
prefetchDefaults: zod.z.object({
|
|
1478
|
+
maxStalenessMs: zod.z.number().int().positive(),
|
|
1479
|
+
timeoutMs: zod.z.number().int().positive(),
|
|
1480
|
+
onError: zod.z.enum(["fallback", "fail"])
|
|
1481
|
+
}).optional(),
|
|
1482
|
+
writeback: zod.z.object({
|
|
1483
|
+
columns: zod.z.array(zod.z.string()),
|
|
1484
|
+
defaultMode: zod.z.enum(["sync", "async"]),
|
|
1485
|
+
timeoutMs: zod.z.number().int().positive(),
|
|
1486
|
+
clearManualOnConfirm: zod.z.boolean()
|
|
1487
|
+
}).optional()
|
|
1488
|
+
});
|
|
1489
|
+
var resolvedMetaIrSchema = zod.z.object({
|
|
1490
|
+
/** Logical pk column of the resolved view (single-column). */
|
|
1491
|
+
pk: zod.z.string(),
|
|
1492
|
+
/** Source name of the manual source. */
|
|
1493
|
+
manual: zod.z.string(),
|
|
1494
|
+
sources: zod.z.array(resolvedSourceIrSchema),
|
|
1495
|
+
deletedBy: zod.z.array(zod.z.string()),
|
|
1496
|
+
/**
|
|
1497
|
+
* Materialized: the view reads `table`, which the data plane refreshes from
|
|
1498
|
+
* the `compute` view (claims-gated) whenever it writes a source row.
|
|
1499
|
+
*/
|
|
1500
|
+
materialized: zod.z.object({ table: zod.z.string(), compute: zod.z.string() }).optional()
|
|
1501
|
+
});
|
|
1502
|
+
var tableMetaIrSchema = zod.z.object({
|
|
1503
|
+
table: zod.z.string(),
|
|
1504
|
+
schema: zod.z.string(),
|
|
1505
|
+
typename: zod.z.string(),
|
|
1506
|
+
/**
|
|
1507
|
+
* True when statements over this relation evaluate a claims-gated synced
|
|
1508
|
+
* view: synced twins (`source: gold.*`) materialize as views whose baked
|
|
1509
|
+
* policy reads `current_setting('request.jwt.claims')`, and namespace views
|
|
1510
|
+
* inherit the gate transitively through their declared dependencies. The
|
|
1511
|
+
* executor must stamp that GUC (transaction-scoped) before any statement
|
|
1512
|
+
* touching such a relation, or the synced arm yields zero rows regardless
|
|
1513
|
+
* of the compiled predicates.
|
|
1514
|
+
*/
|
|
1515
|
+
claimsGated: zod.z.boolean().default(false),
|
|
1516
|
+
columns: zod.z.array(zod.z.object({
|
|
1517
|
+
name: zod.z.string(),
|
|
1518
|
+
columnName: zod.z.string(),
|
|
1519
|
+
type: zod.z.union([zod.z.enum(COLUMN_TYPES), zod.z.object({ enum: zod.z.string(), values: zod.z.array(zod.z.string()) })]),
|
|
1520
|
+
nullable: zod.z.boolean()
|
|
1521
|
+
})),
|
|
1522
|
+
/** Empty for keyless read-only views; keyed tables and views list logical columns. */
|
|
1523
|
+
primaryKey: zod.z.array(zod.z.string()),
|
|
1524
|
+
links: zod.z.array(zod.z.object({
|
|
1525
|
+
name: zod.z.string(),
|
|
1526
|
+
kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
|
|
1527
|
+
target: zod.z.string(),
|
|
1528
|
+
on: zod.z.string(),
|
|
1529
|
+
references: zod.z.string()
|
|
1530
|
+
})),
|
|
1531
|
+
/** §4f whitelists: what dynamic inputs may reference. */
|
|
1532
|
+
filterable: zod.z.array(zod.z.string()),
|
|
1533
|
+
orderable: zod.z.array(zod.z.string()),
|
|
1534
|
+
insertable: zod.z.array(zod.z.string()),
|
|
1535
|
+
settable: zod.z.array(zod.z.string()),
|
|
1536
|
+
appendable: zod.z.array(zod.z.string()),
|
|
1537
|
+
/**
|
|
1538
|
+
* Logical columns stamped with Postgres `now()` on UPDATE (canonical
|
|
1539
|
+
* `onUpdateFn`). Omitted when empty so pre-onUpdate artifacts still parse.
|
|
1540
|
+
*/
|
|
1541
|
+
touchOnUpdate: zod.z.array(zod.z.string()).optional(),
|
|
1542
|
+
/**
|
|
1543
|
+
* Per-cell metadata: logical column → the logical names of its cell-part
|
|
1544
|
+
* columns (which are listed in `columns`, but never filterable, orderable or
|
|
1545
|
+
* writable). Tables stamp `at`/`by` (and a manual source `rev`) on every
|
|
1546
|
+
* write; resolved views expose `source`/`at`/`by`/`conflicts`. Omitted when
|
|
1547
|
+
* the relation tracks no cells.
|
|
1548
|
+
*/
|
|
1549
|
+
cells: zod.z.record(zod.z.string(), zod.z.object({
|
|
1550
|
+
at: zod.z.string().optional(),
|
|
1551
|
+
by: zod.z.string().optional(),
|
|
1552
|
+
rev: zod.z.string().optional(),
|
|
1553
|
+
source: zod.z.string().optional(),
|
|
1554
|
+
conflicts: zod.z.string().optional()
|
|
1555
|
+
}).strict()).optional(),
|
|
1556
|
+
/** Resolved-table view metadata (sources, prefetch/writeback config) for the data plane. */
|
|
1557
|
+
resolved: resolvedMetaIrSchema.optional(),
|
|
1558
|
+
constraints: zod.z.array(zod.z.object({ name: zod.z.string(), columns: zod.z.array(zod.z.string()) })),
|
|
1559
|
+
/** Depth/node caps for dynamic filter trees on this table. */
|
|
1560
|
+
dynamicFilterMaxDepth: zod.z.number().int().positive().default(6),
|
|
1561
|
+
dynamicFilterMaxNodes: zod.z.number().int().positive().default(64)
|
|
1562
|
+
});
|
|
1563
|
+
zod.z.object({
|
|
1564
|
+
irVersion: irVersionSchema,
|
|
1565
|
+
namespace: zod.z.string(),
|
|
1566
|
+
schemaHash: zod.z.string(),
|
|
1567
|
+
opsHash: zod.z.string(),
|
|
1568
|
+
tables: zod.z.record(zod.z.string(), tableMetaIrSchema),
|
|
1569
|
+
operations: zod.z.record(zod.z.string(), compiledOperationSchema)
|
|
1570
|
+
}).superRefine((artifact, ctx) => {
|
|
1571
|
+
for (const [name, operation] of Object.entries(artifact.operations)) {
|
|
1572
|
+
if (operation.irVersion > artifact.irVersion) {
|
|
1573
|
+
ctx.addIssue({ code: "custom", path: ["operations", name, "irVersion"], message: "operation requires a newer IR version than its artifact" });
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
484
1577
|
|
|
485
1578
|
// src/sdl/generate.ts
|
|
486
1579
|
var SdlGenerationError = class extends Error {
|
|
@@ -503,7 +1596,9 @@ var PLATFORM_USER_FIELDS = [
|
|
|
503
1596
|
];
|
|
504
1597
|
function generateSdl(canonical, options = {}) {
|
|
505
1598
|
const def = normalizeCanonical(canonicalDefinitionSchema.parse(canonical));
|
|
506
|
-
const
|
|
1599
|
+
const exposed = canonicalRelations(def).filter((table) => table.source === void 0 && table.internal !== true);
|
|
1600
|
+
const cellsByTable = new Map(exposed.map((table) => [table.table, trackedCells(table)]));
|
|
1601
|
+
const tables = exposed.map((table) => ({ ...table, columns: table.columns.filter((column) => column.cell === void 0) }));
|
|
507
1602
|
if (tables.length === 0) {
|
|
508
1603
|
throw new SdlGenerationError(
|
|
509
1604
|
`Namespace "${def.namespace}" exposes no relations (synced tables are excluded from SDL in v2.0) \u2014 nothing to generate.`
|
|
@@ -559,8 +1654,14 @@ function generateSdl(canonical, options = {}) {
|
|
|
559
1654
|
'"""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
1655
|
);
|
|
561
1656
|
blocks.push("directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT");
|
|
1657
|
+
blocks.push(
|
|
1658
|
+
'"""Abort the entire mutation unless this root affects exactly the document-authored number of rows. Mutation roots only; cannot be conditional."""\ndirective @assertAffectedRows(exactly: Int!) on FIELD'
|
|
1659
|
+
);
|
|
1660
|
+
if (tables.some((table) => table.resolved !== void 0)) blocks.push(...RESOLVED_DIRECTIVE_BLOCKS);
|
|
562
1661
|
for (const scalar of CUSTOM_SCALARS) blocks.push(`scalar ${scalar}`);
|
|
1662
|
+
blocks.push(...renderSharedCellTypes([...cellsByTable.values()]));
|
|
563
1663
|
blocks.push(renderEnum(ORDER_BY_ENUM, [...ORDER_BY_ENUM_VALUES]));
|
|
1664
|
+
blocks.push(renderEnum(DATE_BUCKET_ENUM, DATE_BUCKETS.map((bucket) => bucket.toUpperCase())));
|
|
564
1665
|
for (const enumName of [...usedEnums].sort()) {
|
|
565
1666
|
const entry = enumByName.get(enumName);
|
|
566
1667
|
if (!entry) continue;
|
|
@@ -583,16 +1684,32 @@ function generateSdl(canonical, options = {}) {
|
|
|
583
1684
|
names,
|
|
584
1685
|
namesByTable,
|
|
585
1686
|
emitAggregateBoolExp: aggregateBoolExpTargets.has(table.table),
|
|
586
|
-
pinned: options.pinnedColumns?.[table.table] ?? []
|
|
1687
|
+
pinned: options.pinnedColumns?.[table.table] ?? [],
|
|
1688
|
+
cells: cellsByTable.get(table.table) ?? null
|
|
587
1689
|
})
|
|
588
1690
|
);
|
|
589
1691
|
}
|
|
590
1692
|
blocks.push(renderQueryRoot(tables, namesByTable));
|
|
591
1693
|
const mutationRoot = renderMutationRoot(tables, namesByTable);
|
|
592
1694
|
if (mutationRoot !== null) blocks.push(mutationRoot);
|
|
1695
|
+
assertNoDuplicateTypeNames(blocks);
|
|
593
1696
|
return `${blocks.join("\n\n")}
|
|
594
1697
|
`;
|
|
595
1698
|
}
|
|
1699
|
+
var TYPE_DECLARATION_RE = /^(?:type|input|enum|scalar|interface|union)\s+([_A-Za-z][_0-9A-Za-z]*)/;
|
|
1700
|
+
function assertNoDuplicateTypeNames(blocks) {
|
|
1701
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1702
|
+
for (const block of blocks) {
|
|
1703
|
+
const declared = TYPE_DECLARATION_RE.exec(block)?.[1];
|
|
1704
|
+
if (declared === void 0) continue;
|
|
1705
|
+
if (seen.has(declared)) {
|
|
1706
|
+
throw new SdlGenerationError(
|
|
1707
|
+
`Generated schema declares "${declared}" twice. A canonical enum or table name is PascalCased into this GraphQL type name and collides with another declaration (for example an enum named "date_bucket" renders as "DateBucket", the shared bucket enum). Rename the offending enum or table.`
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
seen.add(declared);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
596
1713
|
function assertEnumValue(value, context) {
|
|
597
1714
|
if (!GRAPHQL_NAME_RE.test(value) || RESERVED_ENUM_VALUES.has(value)) {
|
|
598
1715
|
throw new SdlGenerationError(
|
|
@@ -658,6 +1775,52 @@ ${orderFields}
|
|
|
658
1775
|
}`
|
|
659
1776
|
];
|
|
660
1777
|
}
|
|
1778
|
+
var RESOLVED_DIRECTIVE_BLOCKS = [
|
|
1779
|
+
'"""Refresh this resolved-table root from a prefetch source before reading. Key-addressed roots only; the source must support the root shape."""\ndirective @prefetch(source: String!, maxStaleness: String) repeatable on FIELD',
|
|
1780
|
+
renderEnum("WritebackMode", ["SYNC", "ASYNC"]),
|
|
1781
|
+
'"""Write this resolved-table mutation back to a prefetch source (SYNC rolls back on failure; ASYNC queues)."""\ndirective @writeback(source: String!, mode: WritebackMode) repeatable on FIELD',
|
|
1782
|
+
'"""Declare that this mutation deliberately keeps writeback columns local to the named source."""\ndirective @localOnly(source: String!) repeatable on FIELD'
|
|
1783
|
+
];
|
|
1784
|
+
function trackedCells(table) {
|
|
1785
|
+
const columns = [...new Set(table.columns.flatMap((column) => column.cell ? [column.cell.of] : []))];
|
|
1786
|
+
if (columns.length === 0) return null;
|
|
1787
|
+
return { resolved: table.columns.some((column) => column.cell?.part === "source"), columns };
|
|
1788
|
+
}
|
|
1789
|
+
function renderSharedCellTypes(all) {
|
|
1790
|
+
const present = all.filter((cells) => cells !== null);
|
|
1791
|
+
const blocks = [];
|
|
1792
|
+
if (present.some((cells) => !cells.resolved)) {
|
|
1793
|
+
blocks.push(`"""When, and by whom, a cell was last written."""
|
|
1794
|
+
type ${CELL_TYPENAMES.stamp} {
|
|
1795
|
+
at: timestamptz!
|
|
1796
|
+
by: String
|
|
1797
|
+
}`);
|
|
1798
|
+
}
|
|
1799
|
+
if (present.some((cells) => cells.resolved)) {
|
|
1800
|
+
blocks.push(`"""A newer candidate value that lost to the column's resolve rule."""
|
|
1801
|
+
type ${CELL_TYPENAMES.conflict} {
|
|
1802
|
+
source: String!
|
|
1803
|
+
at: timestamptz
|
|
1804
|
+
value: jsonb
|
|
1805
|
+
}`);
|
|
1806
|
+
blocks.push(`"""How a resolved cell was decided: the winning source, its time and actor, and newer losers."""
|
|
1807
|
+
type ${CELL_TYPENAMES.resolved} {
|
|
1808
|
+
source: String!
|
|
1809
|
+
at: timestamptz
|
|
1810
|
+
by: String
|
|
1811
|
+
conflicts: [${CELL_TYPENAMES.conflict}!]!
|
|
1812
|
+
}`);
|
|
1813
|
+
}
|
|
1814
|
+
return blocks;
|
|
1815
|
+
}
|
|
1816
|
+
function renderCellsType(ctx) {
|
|
1817
|
+
if (!ctx.cells) return [];
|
|
1818
|
+
const cellType = ctx.cells.resolved ? CELL_TYPENAMES.resolved : CELL_TYPENAMES.stamp;
|
|
1819
|
+
const lines = ctx.cells.columns.map((column) => ` ${fieldName(column)}: ${cellType}`);
|
|
1820
|
+
return [`type ${cellsTypename(ctx.names.typename)} {
|
|
1821
|
+
${lines.join("\n")}
|
|
1822
|
+
}`];
|
|
1823
|
+
}
|
|
661
1824
|
function relationArgs(target) {
|
|
662
1825
|
return `where: ${target.boolExp}, orderBy: [${target.orderBy}!], limit: Int, offset: Int`;
|
|
663
1826
|
}
|
|
@@ -666,10 +1829,10 @@ function rootArgs(names) {
|
|
|
666
1829
|
}
|
|
667
1830
|
function primaryKeyColumns(table) {
|
|
668
1831
|
if (!table.primaryKey || table.primaryKey.length === 0) return [];
|
|
669
|
-
return table.primaryKey.map((
|
|
670
|
-
const column = table.columns.find((c) => c.columnName ===
|
|
1832
|
+
return table.primaryKey.map((identifier3) => {
|
|
1833
|
+
const column = table.columns.find((c) => c.columnName === identifier3 || c.name === identifier3);
|
|
671
1834
|
if (!column) {
|
|
672
|
-
throw new SdlGenerationError(`Primary key column "${
|
|
1835
|
+
throw new SdlGenerationError(`Primary key column "${identifier3}" not found on table "${table.table}".`);
|
|
673
1836
|
}
|
|
674
1837
|
return column;
|
|
675
1838
|
});
|
|
@@ -678,10 +1841,12 @@ function renderTableBlocks(ctx) {
|
|
|
678
1841
|
const { table, names } = ctx;
|
|
679
1842
|
const blocks = [
|
|
680
1843
|
renderObjectType(ctx),
|
|
1844
|
+
...renderCellsType(ctx),
|
|
681
1845
|
renderSelectColumnEnum(ctx),
|
|
682
1846
|
renderOrderByInput(ctx),
|
|
683
1847
|
renderBoolExp(ctx),
|
|
684
|
-
...renderAggregateTypes(ctx)
|
|
1848
|
+
...renderAggregateTypes(ctx),
|
|
1849
|
+
...renderGroupByTypes(ctx)
|
|
685
1850
|
];
|
|
686
1851
|
if (ctx.emitAggregateBoolExp) blocks.push(...renderAggregateBoolExp(ctx));
|
|
687
1852
|
const pk = primaryKeyColumns(table);
|
|
@@ -691,11 +1856,49 @@ function renderTableBlocks(ctx) {
|
|
|
691
1856
|
${fields}
|
|
692
1857
|
}`);
|
|
693
1858
|
}
|
|
694
|
-
if (
|
|
1859
|
+
if (ctx.table.resolved !== void 0) {
|
|
1860
|
+
blocks.push(...renderResolvedWriteBlocks(ctx));
|
|
1861
|
+
} else if (allowsInsert(table) || allowsUpdate(table)) {
|
|
695
1862
|
blocks.push(...renderWriteBlocks(ctx, { insert: allowsInsert(table), update: allowsUpdate(table) }));
|
|
696
1863
|
}
|
|
697
1864
|
return blocks;
|
|
698
1865
|
}
|
|
1866
|
+
function resolvedWritableColumns(table) {
|
|
1867
|
+
const system = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((column) => column.name));
|
|
1868
|
+
return table.columns.filter((column) => !system.has(column.name));
|
|
1869
|
+
}
|
|
1870
|
+
function renderResolvedWriteBlocks(ctx) {
|
|
1871
|
+
const { table, names } = ctx;
|
|
1872
|
+
const writable = resolvedWritableColumns(table);
|
|
1873
|
+
const pk = new Set(table.primaryKey ?? []);
|
|
1874
|
+
const settable = writable.filter((column) => !pk.has(column.columnName));
|
|
1875
|
+
const line = (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`;
|
|
1876
|
+
const blocks = [];
|
|
1877
|
+
if (allowsInsert(table)) blocks.push(`input ${names.insertInput} {
|
|
1878
|
+
${writable.map(line).join("\n")}
|
|
1879
|
+
}`);
|
|
1880
|
+
if (allowsUpdate(table)) {
|
|
1881
|
+
blocks.push(`input ${names.setInput} {
|
|
1882
|
+
${settable.map(line).join("\n")}
|
|
1883
|
+
}`);
|
|
1884
|
+
blocks.push(renderEnum(names.updateColumnEnum, settable.map((column) => fieldName(column.name))));
|
|
1885
|
+
}
|
|
1886
|
+
return blocks;
|
|
1887
|
+
}
|
|
1888
|
+
function renderResolvedMutationFields(table, names) {
|
|
1889
|
+
const lines = [];
|
|
1890
|
+
const pk = primaryKeyColumns(table);
|
|
1891
|
+
if (allowsInsert(table)) lines.push(` ${names.insertOneRoot}(object: ${names.insertInput}!): ${names.typename}`);
|
|
1892
|
+
if (allowsUpdate(table) && pk.length > 0) {
|
|
1893
|
+
lines.push(` ${names.updateByPkRoot}(pkColumns: ${names.pkColumnsInput}!, _set: ${names.setInput}!): ${names.typename}`);
|
|
1894
|
+
lines.push(` ${names.revertByPkRoot}(pkColumns: ${names.pkColumnsInput}!, columns: [${names.updateColumnEnum}!]!): ${names.typename}`);
|
|
1895
|
+
if ((table.resolved?.deletedBy.length ?? 0) > 0) {
|
|
1896
|
+
const args = pk.map((column) => `${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}!`).join(", ");
|
|
1897
|
+
lines.push(` ${names.deleteByPkRoot}(${args}): ${names.typename}`);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
return lines;
|
|
1901
|
+
}
|
|
699
1902
|
function allowsInsert(table) {
|
|
700
1903
|
return table.policies.write !== void 0 || table.policies.insert !== void 0;
|
|
701
1904
|
}
|
|
@@ -718,6 +1921,10 @@ function renderObjectType(ctx) {
|
|
|
718
1921
|
const deprecated = column.deprecated ? " @deprecated" : "";
|
|
719
1922
|
lines.push(` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}${bang}${deprecated}`);
|
|
720
1923
|
}
|
|
1924
|
+
if (ctx.cells) {
|
|
1925
|
+
lines.push(' """Per-cell metadata: when each field was last written, and by whom."""');
|
|
1926
|
+
lines.push(` ${CELLS_FIELD}: ${cellsTypename(names.typename)}!`);
|
|
1927
|
+
}
|
|
721
1928
|
for (const link of table.links) {
|
|
722
1929
|
if (link.description !== void 0) lines.push(...descriptionLines(link.description, " "));
|
|
723
1930
|
if (link.kind === "toPlatformUser") {
|
|
@@ -786,7 +1993,10 @@ function numericishColumns(table) {
|
|
|
786
1993
|
return table.columns.filter((column) => typeof column.type !== "object" && NUMERICISH.has(column.type));
|
|
787
1994
|
}
|
|
788
1995
|
function jsonbColumns(table) {
|
|
789
|
-
return table.
|
|
1996
|
+
return clientWritableColumns(table).filter((column) => column.type === "jsonb");
|
|
1997
|
+
}
|
|
1998
|
+
function clientWritableColumns(table) {
|
|
1999
|
+
return table.columns;
|
|
790
2000
|
}
|
|
791
2001
|
function renderAggregateTypes(ctx) {
|
|
792
2002
|
const { table, names } = ctx;
|
|
@@ -818,6 +2028,32 @@ ${sumLines.join("\n")}
|
|
|
818
2028
|
}
|
|
819
2029
|
return blocks;
|
|
820
2030
|
}
|
|
2031
|
+
function groupableColumns(table) {
|
|
2032
|
+
return table.columns.filter((column) => column.type !== "jsonb");
|
|
2033
|
+
}
|
|
2034
|
+
function renderGroupByTypes(ctx) {
|
|
2035
|
+
const { table, names } = ctx;
|
|
2036
|
+
const groupable = groupableColumns(table);
|
|
2037
|
+
if (groupable.length === 0) return [];
|
|
2038
|
+
const keyLines = groupable.map((column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`);
|
|
2039
|
+
return [
|
|
2040
|
+
renderEnum(names.groupByColumnEnum, groupable.map((column) => fieldName(column.name))),
|
|
2041
|
+
`input ${names.groupByInput} {
|
|
2042
|
+
column: ${names.groupByColumnEnum}!
|
|
2043
|
+
bucket: ${DATE_BUCKET_ENUM}
|
|
2044
|
+
}`,
|
|
2045
|
+
`type ${names.groupKey} {
|
|
2046
|
+
${keyLines.join("\n")}
|
|
2047
|
+
}`,
|
|
2048
|
+
`type ${names.aggregateGroup} {
|
|
2049
|
+
key: ${names.groupKey}!
|
|
2050
|
+
aggregate: ${names.aggregateFields}
|
|
2051
|
+
}`
|
|
2052
|
+
];
|
|
2053
|
+
}
|
|
2054
|
+
function groupRootArgs(names) {
|
|
2055
|
+
return `where: ${names.boolExp}, groupBy: [${names.groupByInput}!]!, limit: Int, offset: Int`;
|
|
2056
|
+
}
|
|
821
2057
|
function renderAggregateBoolExp(ctx) {
|
|
822
2058
|
const { names } = ctx;
|
|
823
2059
|
return [
|
|
@@ -843,12 +2079,15 @@ function constraintValues(table) {
|
|
|
843
2079
|
}
|
|
844
2080
|
return [...values].sort();
|
|
845
2081
|
}
|
|
2082
|
+
function clientSettableColumns(table) {
|
|
2083
|
+
return clientWritableColumns(table).filter((column) => column.onUpdateFn === void 0);
|
|
2084
|
+
}
|
|
846
2085
|
function renderWriteBlocks(ctx, access) {
|
|
847
2086
|
const { table, names, pinned } = ctx;
|
|
848
2087
|
const blocks = [];
|
|
849
2088
|
if (access.insert) {
|
|
850
2089
|
const pinnedSet = new Set(pinned);
|
|
851
|
-
const insertable = table.
|
|
2090
|
+
const insertable = clientWritableColumns(table).filter(
|
|
852
2091
|
(column) => !pinnedSet.has(column.name) && !pinnedSet.has(column.columnName)
|
|
853
2092
|
);
|
|
854
2093
|
if (insertable.length === 0) {
|
|
@@ -864,7 +2103,7 @@ ${insertLines.join("\n")}
|
|
|
864
2103
|
}`);
|
|
865
2104
|
}
|
|
866
2105
|
if (access.update) {
|
|
867
|
-
const setLines = table.
|
|
2106
|
+
const setLines = clientSettableColumns(table).map(
|
|
868
2107
|
(column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
|
|
869
2108
|
);
|
|
870
2109
|
blocks.push(`input ${names.setInput} {
|
|
@@ -891,7 +2130,7 @@ ${incLines.join("\n")}
|
|
|
891
2130
|
if (access.insert && allowsOnConflict(table) && constraints.length > 0) {
|
|
892
2131
|
for (const value of constraints) assertEnumValue(value, `constraint enum for table "${table.table}"`);
|
|
893
2132
|
blocks.push(renderEnum(names.constraintEnum, constraints));
|
|
894
|
-
const updateColumns = table.
|
|
2133
|
+
const updateColumns = clientSettableColumns(table).map((column) => fieldName(column.name));
|
|
895
2134
|
blocks.push(renderEnum(names.updateColumnEnum, updateColumns));
|
|
896
2135
|
blocks.push(
|
|
897
2136
|
`input ${names.onConflict} {
|
|
@@ -923,6 +2162,9 @@ function renderQueryRoot(tables, namesByTable) {
|
|
|
923
2162
|
lines.push(` ${names.byPkRoot}(${args}): ${names.typename}`);
|
|
924
2163
|
}
|
|
925
2164
|
lines.push(` ${names.aggregateRoot}(${rootArgs(names)}): ${names.aggregate}!`);
|
|
2165
|
+
if (groupableColumns(table).length > 0) {
|
|
2166
|
+
lines.push(` ${names.aggregateGroupsRoot}(${groupRootArgs(names)}): [${names.aggregateGroup}!]!`);
|
|
2167
|
+
}
|
|
926
2168
|
}
|
|
927
2169
|
lines.push("}");
|
|
928
2170
|
return lines.join("\n");
|
|
@@ -938,6 +2180,10 @@ function renderMutationRoot(tables, namesByTable) {
|
|
|
938
2180
|
if (!canInsert && !canUpdate) continue;
|
|
939
2181
|
const names = namesByTable.get(table.table);
|
|
940
2182
|
if (!names) continue;
|
|
2183
|
+
if (table.resolved !== void 0) {
|
|
2184
|
+
lines.push(...renderResolvedMutationFields(table, names));
|
|
2185
|
+
continue;
|
|
2186
|
+
}
|
|
941
2187
|
const constraints = constraintValues(table);
|
|
942
2188
|
const onConflictArg = canInsert && allowsOnConflict(table) && constraints.length > 0 ? `, onConflict: ${names.onConflict}` : "";
|
|
943
2189
|
const updateArgs = [`_set: ${names.setInput}`];
|
|
@@ -1071,13 +2317,13 @@ function generateTypePolicies(canonical) {
|
|
|
1071
2317
|
const entries = [];
|
|
1072
2318
|
let hasPlatformUser = false;
|
|
1073
2319
|
for (const table of canonicalRelations(def)) {
|
|
1074
|
-
if (table.source !== void 0) continue;
|
|
2320
|
+
if (table.source !== void 0 || table.internal === true) continue;
|
|
1075
2321
|
if (table.links.some((link) => link.kind === "toPlatformUser")) hasPlatformUser = true;
|
|
1076
2322
|
if (!table.primaryKey || table.primaryKey.length === 0) continue;
|
|
1077
|
-
const keyFields = table.primaryKey.map((
|
|
1078
|
-
const column = table.columns.find((c) => c.columnName ===
|
|
2323
|
+
const keyFields = table.primaryKey.map((identifier3) => {
|
|
2324
|
+
const column = table.columns.find((c) => c.columnName === identifier3 || c.name === identifier3);
|
|
1079
2325
|
if (!column) {
|
|
1080
|
-
throw new CodegenGqlError(`Primary key column "${
|
|
2326
|
+
throw new CodegenGqlError(`Primary key column "${identifier3}" not found on relation "${table.table}".`);
|
|
1081
2327
|
}
|
|
1082
2328
|
return fieldName(column.name);
|
|
1083
2329
|
});
|
|
@@ -1095,7 +2341,10 @@ ${body}
|
|
|
1095
2341
|
`;
|
|
1096
2342
|
}
|
|
1097
2343
|
|
|
2344
|
+
exports.BUCKETABLE_COLUMN_TYPES = BUCKETABLE_COLUMN_TYPES;
|
|
2345
|
+
exports.CELL_TYPENAMES = CELL_TYPENAMES;
|
|
1098
2346
|
exports.CUSTOM_SCALARS = CUSTOM_SCALARS;
|
|
2347
|
+
exports.DATE_BUCKET_ENUM = DATE_BUCKET_ENUM;
|
|
1099
2348
|
exports.MANIFEST_VERSION = MANIFEST_VERSION;
|
|
1100
2349
|
exports.NUMERICISH_COLUMN_TYPES = NUMERICISH_COLUMN_TYPES;
|
|
1101
2350
|
exports.ORDER_BY_ENUM = ORDER_BY_ENUM;
|
|
@@ -1105,6 +2354,7 @@ exports.SdlGenerationError = SdlGenerationError;
|
|
|
1105
2354
|
exports.VARIABLE_SCALAR_NAMES = VARIABLE_SCALAR_NAMES;
|
|
1106
2355
|
exports.buildOperationsManifest = buildOperationsManifest;
|
|
1107
2356
|
exports.camelCase = camelCase;
|
|
2357
|
+
exports.cellsTypename = cellsTypename;
|
|
1108
2358
|
exports.columnTypeForScalar = columnTypeForScalar;
|
|
1109
2359
|
exports.comparisonExpName = comparisonExpName;
|
|
1110
2360
|
exports.enumTypeName = enumTypeName;
|
|
@@ -1116,6 +2366,7 @@ exports.manifestOperationSchema = manifestOperationSchema;
|
|
|
1116
2366
|
exports.manifestRequiresSchema = manifestRequiresSchema;
|
|
1117
2367
|
exports.operationsManifestSchema = operationsManifestSchema;
|
|
1118
2368
|
exports.pascalCase = pascalCase;
|
|
2369
|
+
exports.resolvedWritableColumns = resolvedWritableColumns;
|
|
1119
2370
|
exports.tableNames = tableNames;
|
|
1120
2371
|
exports.typeName = typeName;
|
|
1121
2372
|
//# sourceMappingURL=sdl.cjs.map
|