@sequenceholdings/orm 0.1.0 → 0.1.2
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 +1 -1
- package/dist/authoring.cjs +794 -0
- package/dist/authoring.cjs.map +1 -0
- package/dist/authoring.d.cts +274 -0
- package/dist/authoring.d.ts +274 -0
- package/dist/authoring.js +763 -0
- package/dist/authoring.js.map +1 -0
- package/dist/canonical.cjs +146 -0
- package/dist/canonical.cjs.map +1 -0
- package/dist/canonical.d.cts +369 -0
- package/dist/canonical.d.ts +369 -0
- package/dist/canonical.js +131 -0
- package/dist/canonical.js.map +1 -0
- package/dist/cli.cjs +6208 -1693
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +8 -1
- package/dist/cli.d.ts +8 -1
- package/dist/cli.js +6262 -1749
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs +120 -0
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +74 -2
- package/dist/client.d.ts +74 -2
- package/dist/client.js +116 -1
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +3001 -0
- package/dist/compiler.cjs.map +1 -0
- package/dist/compiler.d.cts +581 -0
- package/dist/compiler.d.ts +581 -0
- package/dist/compiler.js +2947 -0
- package/dist/compiler.js.map +1 -0
- package/dist/index.cjs +1730 -327
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +126 -56
- package/dist/index.d.ts +126 -56
- package/dist/index.js +1643 -324
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +407 -0
- package/dist/ir.cjs.map +1 -0
- package/dist/ir.d.cts +1179 -0
- package/dist/ir.d.ts +1179 -0
- package/dist/ir.js +388 -0
- package/dist/ir.js.map +1 -0
- package/dist/naming-CTvWSRTf.d.ts +79 -0
- package/dist/naming-DRyinU4u.d.cts +79 -0
- package/dist/sdl.cjs +833 -0
- package/dist/sdl.cjs.map +1 -0
- package/dist/sdl.d.cts +96 -0
- package/dist/sdl.d.ts +96 -0
- package/dist/sdl.js +809 -0
- package/dist/sdl.js.map +1 -0
- package/package.json +36 -3
- package/src/codegen-gql/apollo-hooks.cjs +98 -0
- package/src/codegen-gql/server-fns.cjs +101 -0
- package/templates/README.md +13 -0
- package/templates/orm-namespace/CLAUDE.md +81 -0
- package/templates/orm-namespace/codegen.ts +22 -0
- package/templates/orm-namespace/gitignore +5 -0
- package/templates/orm-namespace/graphql/example/queries.ts +56 -0
- package/templates/orm-namespace/package.json +24 -0
- package/templates/orm-namespace/schema/tables.ts +71 -0
- package/templates/orm-namespace/sequence.config.ts +32 -0
- package/templates/orm-namespace/tsconfig.json +15 -0
package/dist/index.cjs
CHANGED
|
@@ -1,13 +1,865 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var crypto = require('crypto');
|
|
3
4
|
var zod = require('zod');
|
|
5
|
+
var jiti = require('jiti');
|
|
6
|
+
var promises = require('fs/promises');
|
|
7
|
+
var module$1 = require('module');
|
|
8
|
+
var path = require('path');
|
|
4
9
|
var api = require('drizzle-kit/api');
|
|
5
10
|
var pgCore = require('drizzle-orm/pg-core');
|
|
6
11
|
var drizzleOrm = require('drizzle-orm');
|
|
7
|
-
var crypto = require('crypto');
|
|
8
|
-
var promises = require('fs/promises');
|
|
9
|
-
var path = require('path');
|
|
10
12
|
var jsYaml = require('js-yaml');
|
|
13
|
+
var customRolesManifest = require('@sequenceholdings/custom-roles-manifest');
|
|
14
|
+
|
|
15
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
16
|
+
// src/authoring/columns.ts
|
|
17
|
+
var COLUMN_BRAND = "seq-column";
|
|
18
|
+
var ENUM_BRAND = "seq-enum";
|
|
19
|
+
function isEnumRef(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && value.kind === ENUM_BRAND;
|
|
21
|
+
}
|
|
22
|
+
var COLUMN_DEFAULT_FNS = ["gen_random_uuid()", "now()"];
|
|
23
|
+
function deriveColumnName(propertyName) {
|
|
24
|
+
return propertyName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
var ColumnBuilder = class {
|
|
27
|
+
kind = COLUMN_BRAND;
|
|
28
|
+
state;
|
|
29
|
+
constructor(type, columnName) {
|
|
30
|
+
this.state = {
|
|
31
|
+
type,
|
|
32
|
+
columnName,
|
|
33
|
+
primaryKey: false,
|
|
34
|
+
notNull: false,
|
|
35
|
+
unique: false,
|
|
36
|
+
hasDefault: false,
|
|
37
|
+
defaultValue: void 0,
|
|
38
|
+
defaultFn: void 0,
|
|
39
|
+
description: void 0,
|
|
40
|
+
renamedFrom: void 0,
|
|
41
|
+
cast: void 0,
|
|
42
|
+
deprecated: false
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Marks this column as (part of) the primary key. Implies NOT NULL. */
|
|
46
|
+
primaryKey() {
|
|
47
|
+
this.state.primaryKey = true;
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
notNull() {
|
|
51
|
+
this.state.notNull = true;
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
unique() {
|
|
55
|
+
this.state.unique = true;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Column default: a literal, or one of the allowlisted SQL functions
|
|
60
|
+
* (`'gen_random_uuid()'` / `'now()'`), which serialize as `defaultFn`.
|
|
61
|
+
*/
|
|
62
|
+
default(value) {
|
|
63
|
+
if (value === "gen_random_uuid()" || value === "now()") {
|
|
64
|
+
this.state.defaultFn = value;
|
|
65
|
+
this.state.hasDefault = false;
|
|
66
|
+
this.state.defaultValue = void 0;
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
this.state.hasDefault = true;
|
|
70
|
+
this.state.defaultValue = value;
|
|
71
|
+
this.state.defaultFn = void 0;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
description(text3) {
|
|
75
|
+
this.state.description = text3;
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
/** Migration rename hint: the previous Postgres column name. */
|
|
79
|
+
renamedFrom(previousColumnName) {
|
|
80
|
+
this.state.renamedFrom = previousColumnName;
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
/** Migration cast hint: the `USING` SQL applied when this column changes type. */
|
|
84
|
+
cast(usingSql) {
|
|
85
|
+
this.state.cast = usingSql;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
/** Emits `@deprecated` in the generated SDL; the column stays queryable. */
|
|
89
|
+
deprecated() {
|
|
90
|
+
this.state.deprecated = true;
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
/** Whether `.primaryKey()` was called — collected into the table-level PK. */
|
|
94
|
+
get isPrimaryKey() {
|
|
95
|
+
return this.state.primaryKey;
|
|
96
|
+
}
|
|
97
|
+
/** The enum name when this is an enum column, else undefined. */
|
|
98
|
+
get enumName() {
|
|
99
|
+
return typeof this.state.type === "object" ? this.state.type.enum : void 0;
|
|
100
|
+
}
|
|
101
|
+
/** Postgres column name: explicit override, else snake_case of the property name. */
|
|
102
|
+
columnNameFor(propertyName) {
|
|
103
|
+
return this.state.columnName ?? deriveColumnName(propertyName);
|
|
104
|
+
}
|
|
105
|
+
/** Serialize to a canonical v2 column. Validation happens in serializeNamespace. */
|
|
106
|
+
toCanonicalColumn(propertyName) {
|
|
107
|
+
const column = {
|
|
108
|
+
name: propertyName,
|
|
109
|
+
columnName: this.columnNameFor(propertyName),
|
|
110
|
+
type: typeof this.state.type === "object" ? { enum: this.state.type.enum } : this.state.type,
|
|
111
|
+
nullable: !(this.state.notNull || this.state.primaryKey),
|
|
112
|
+
unique: this.state.unique,
|
|
113
|
+
deprecated: this.state.deprecated
|
|
114
|
+
};
|
|
115
|
+
if (this.state.hasDefault) column.default = this.state.defaultValue ?? null;
|
|
116
|
+
if (this.state.defaultFn !== void 0) column.defaultFn = this.state.defaultFn;
|
|
117
|
+
if (this.state.description !== void 0) column.description = this.state.description;
|
|
118
|
+
if (this.state.renamedFrom !== void 0) column.renamedFrom = this.state.renamedFrom;
|
|
119
|
+
if (this.state.cast !== void 0) column.cast = this.state.cast;
|
|
120
|
+
return column;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
function isColumnBuilder(value) {
|
|
124
|
+
return typeof value === "object" && value !== null && value.kind === COLUMN_BRAND;
|
|
125
|
+
}
|
|
126
|
+
var scalar = (type) => (columnName) => new ColumnBuilder(type, columnName);
|
|
127
|
+
var t = {
|
|
128
|
+
uuid: scalar("uuid"),
|
|
129
|
+
text: scalar("text"),
|
|
130
|
+
integer: scalar("integer"),
|
|
131
|
+
bigint: scalar("bigint"),
|
|
132
|
+
numeric: scalar("numeric"),
|
|
133
|
+
double: scalar("double"),
|
|
134
|
+
boolean: scalar("boolean"),
|
|
135
|
+
timestamptz: scalar("timestamptz"),
|
|
136
|
+
date: scalar("date"),
|
|
137
|
+
jsonb: scalar("jsonb"),
|
|
138
|
+
enum: (ref, columnName) => new ColumnBuilder({ enum: ref.name }, columnName)
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/constants.ts
|
|
142
|
+
var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
|
|
143
|
+
var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
144
|
+
var MAX_IDENTIFIER_LENGTH = 63;
|
|
145
|
+
var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
|
|
146
|
+
var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
|
|
147
|
+
var CAPABILITY_KEY_PATTERN = /^(?:org\/)?[a-z0-9-]{1,64}$/;
|
|
148
|
+
var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
149
|
+
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/;
|
|
150
|
+
var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
|
|
151
|
+
var ORM_SCHEMA_PREFIX = "orm_";
|
|
152
|
+
var CONSUMER_ROLE_SUFFIX = "_consumer";
|
|
153
|
+
var ormSchemaName = (namespace) => `${ORM_SCHEMA_PREFIX}${namespace}`;
|
|
154
|
+
var consumerRoleName = (namespace) => `${ORM_SCHEMA_PREFIX}${namespace}${CONSUMER_ROLE_SUFFIX}`;
|
|
155
|
+
var DEFINITION_FILE_EXTENSIONS = [".yml", ".yaml"];
|
|
156
|
+
var NAMESPACE_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["namespace.yaml", "namespace.yml"]);
|
|
157
|
+
var HASH_DOMAIN = "sequence-orm-definitions\0";
|
|
158
|
+
var MAX_DEFINITION_FILE_BYTES = 1024 * 1024;
|
|
159
|
+
var RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set(["actions", "query"]);
|
|
160
|
+
var RESERVED_COLUMN_NAMES = /* @__PURE__ */ new Set(["select", "order", "limit", "offset"]);
|
|
161
|
+
|
|
162
|
+
// src/errors.ts
|
|
163
|
+
var OrmDefinitionError = class extends Error {
|
|
164
|
+
constructor(message, file) {
|
|
165
|
+
super(file ? `${file}: ${message}` : message);
|
|
166
|
+
this.file = file;
|
|
167
|
+
}
|
|
168
|
+
name = "OrmDefinitionError";
|
|
169
|
+
};
|
|
170
|
+
var PredicateParseError = class extends Error {
|
|
171
|
+
constructor(message, position) {
|
|
172
|
+
super(`${message} (at position ${position})`);
|
|
173
|
+
this.position = position;
|
|
174
|
+
}
|
|
175
|
+
name = "PredicateParseError";
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/authoring/registry.ts
|
|
179
|
+
var CONTEXT_STACK_KEY = Symbol.for("sequence.orm.authoring.context-stack.v2");
|
|
180
|
+
function contextStack() {
|
|
181
|
+
const holder = globalThis;
|
|
182
|
+
const existing = holder[CONTEXT_STACK_KEY];
|
|
183
|
+
if (Array.isArray(existing)) {
|
|
184
|
+
return existing;
|
|
185
|
+
}
|
|
186
|
+
const created = [];
|
|
187
|
+
holder[CONTEXT_STACK_KEY] = created;
|
|
188
|
+
return created;
|
|
189
|
+
}
|
|
190
|
+
function beginAuthoringContext() {
|
|
191
|
+
const context = { currentFile: void 0, tables: [], enums: [], configs: [] };
|
|
192
|
+
contextStack().push(context);
|
|
193
|
+
return context;
|
|
194
|
+
}
|
|
195
|
+
function endAuthoringContext(context) {
|
|
196
|
+
const stack = contextStack();
|
|
197
|
+
const top = stack[stack.length - 1];
|
|
198
|
+
if (top !== context) {
|
|
199
|
+
throw new Error("endAuthoringContext: the given context is not the active one \u2014 begin/end calls are mismatched");
|
|
200
|
+
}
|
|
201
|
+
stack.pop();
|
|
202
|
+
}
|
|
203
|
+
function activeAuthoringContext() {
|
|
204
|
+
const stack = contextStack();
|
|
205
|
+
return stack[stack.length - 1];
|
|
206
|
+
}
|
|
207
|
+
function register(bucket, value) {
|
|
208
|
+
const context = activeAuthoringContext();
|
|
209
|
+
if (!context) return;
|
|
210
|
+
context[bucket].push({ value, file: context.currentFile });
|
|
211
|
+
}
|
|
212
|
+
function registerTable(value) {
|
|
213
|
+
register("tables", value);
|
|
214
|
+
}
|
|
215
|
+
function registerEnum(value) {
|
|
216
|
+
register("enums", value);
|
|
217
|
+
}
|
|
218
|
+
function registerNamespaceConfig(value) {
|
|
219
|
+
register("configs", value);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/authoring/table.ts
|
|
223
|
+
var TABLE_BRAND = "seq-table";
|
|
224
|
+
var LINK_BRAND = "seq-link";
|
|
225
|
+
function toOne(target, options) {
|
|
226
|
+
return {
|
|
227
|
+
kind: LINK_BRAND,
|
|
228
|
+
linkKind: "toOne",
|
|
229
|
+
target,
|
|
230
|
+
on: options.on,
|
|
231
|
+
references: options.references,
|
|
232
|
+
enforce: options.enforce ?? false,
|
|
233
|
+
description: options.description
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function toMany(target, options) {
|
|
237
|
+
return {
|
|
238
|
+
kind: LINK_BRAND,
|
|
239
|
+
linkKind: "toMany",
|
|
240
|
+
target,
|
|
241
|
+
on: options.on,
|
|
242
|
+
references: options.references,
|
|
243
|
+
enforce: false,
|
|
244
|
+
description: options.description
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function toPlatformUser(options) {
|
|
248
|
+
return {
|
|
249
|
+
kind: LINK_BRAND,
|
|
250
|
+
linkKind: "toPlatformUser",
|
|
251
|
+
target: void 0,
|
|
252
|
+
on: options.on,
|
|
253
|
+
references: options.references,
|
|
254
|
+
enforce: false,
|
|
255
|
+
description: options.description
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function isSeqLink(value) {
|
|
259
|
+
return typeof value === "object" && value !== null && value.kind === LINK_BRAND;
|
|
260
|
+
}
|
|
261
|
+
function isSeqTable(value) {
|
|
262
|
+
return typeof value === "object" && value !== null && value.kind === TABLE_BRAND;
|
|
263
|
+
}
|
|
264
|
+
function assertIdentifier({ value, what, file }) {
|
|
265
|
+
if (!IDENTIFIER_PATTERN.test(value) || value.length > MAX_IDENTIFIER_LENGTH) {
|
|
266
|
+
throw new OrmDefinitionError(
|
|
267
|
+
`${what} "${value}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
|
|
268
|
+
file
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function defineTable(name, columns, config = {}) {
|
|
273
|
+
const file = activeAuthoringContext()?.currentFile;
|
|
274
|
+
assertIdentifier({ value: name, what: "table name", file });
|
|
275
|
+
const entries = Object.entries(columns);
|
|
276
|
+
if (entries.length === 0) {
|
|
277
|
+
throw new OrmDefinitionError(`table "${name}" declares no columns`, file);
|
|
278
|
+
}
|
|
279
|
+
const seenColumnNames = /* @__PURE__ */ new Map();
|
|
280
|
+
for (const [propertyName, builder] of entries) {
|
|
281
|
+
if (!isColumnBuilder(builder)) {
|
|
282
|
+
throw new OrmDefinitionError(
|
|
283
|
+
`table "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
|
|
284
|
+
file
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
const columnName = builder.columnNameFor(propertyName);
|
|
288
|
+
assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file });
|
|
289
|
+
const existing = seenColumnNames.get(columnName);
|
|
290
|
+
if (existing !== void 0) {
|
|
291
|
+
throw new OrmDefinitionError(
|
|
292
|
+
`table "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
|
|
293
|
+
file
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
seenColumnNames.set(columnName, propertyName);
|
|
297
|
+
}
|
|
298
|
+
const table = { kind: TABLE_BRAND, tableName: name, columns, config, source: void 0, file };
|
|
299
|
+
registerTable(table);
|
|
300
|
+
return table;
|
|
301
|
+
}
|
|
302
|
+
function defineSyncedTable(name, config) {
|
|
303
|
+
const file = activeAuthoringContext()?.currentFile;
|
|
304
|
+
assertIdentifier({ value: name, what: "table name", file });
|
|
305
|
+
if (!SYNCED_SOURCE_PATTERN.test(config.source)) {
|
|
306
|
+
throw new OrmDefinitionError(
|
|
307
|
+
`synced table "${name}": source "${config.source}" must match ${SYNCED_SOURCE_PATTERN} (gold layer only)`,
|
|
308
|
+
file
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
const table = {
|
|
312
|
+
kind: TABLE_BRAND,
|
|
313
|
+
tableName: name,
|
|
314
|
+
columns: {},
|
|
315
|
+
config: config.description !== void 0 ? { description: config.description } : {},
|
|
316
|
+
source: config.source,
|
|
317
|
+
file
|
|
318
|
+
};
|
|
319
|
+
registerTable(table);
|
|
320
|
+
return table;
|
|
321
|
+
}
|
|
322
|
+
function defineEnum(name, values) {
|
|
323
|
+
const file = activeAuthoringContext()?.currentFile;
|
|
324
|
+
assertIdentifier({ value: name, what: "enum name", file });
|
|
325
|
+
if (values.length === 0 || values.length > 100) {
|
|
326
|
+
throw new OrmDefinitionError(`enum "${name}" must declare between 1 and 100 values`, file);
|
|
327
|
+
}
|
|
328
|
+
const seen = /* @__PURE__ */ new Set();
|
|
329
|
+
for (const value of values) {
|
|
330
|
+
if (value.length === 0 || value.length > MAX_IDENTIFIER_LENGTH) {
|
|
331
|
+
throw new OrmDefinitionError(
|
|
332
|
+
`enum "${name}": value ${JSON.stringify(value)} must be 1-${MAX_IDENTIFIER_LENGTH} characters`,
|
|
333
|
+
file
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
if (seen.has(value)) {
|
|
337
|
+
throw new OrmDefinitionError(`enum "${name}" declares duplicate value "${value}"`, file);
|
|
338
|
+
}
|
|
339
|
+
seen.add(value);
|
|
340
|
+
}
|
|
341
|
+
const ref = { kind: ENUM_BRAND, name, values: [...values] };
|
|
342
|
+
registerEnum(ref);
|
|
343
|
+
return ref;
|
|
344
|
+
}
|
|
345
|
+
var CANONICAL_FORMAT = 2;
|
|
346
|
+
var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
|
|
347
|
+
var identifier = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
|
|
348
|
+
var COLUMN_TYPES = [
|
|
349
|
+
"uuid",
|
|
350
|
+
"text",
|
|
351
|
+
"integer",
|
|
352
|
+
"bigint",
|
|
353
|
+
"numeric",
|
|
354
|
+
"double",
|
|
355
|
+
"boolean",
|
|
356
|
+
"timestamptz",
|
|
357
|
+
"date",
|
|
358
|
+
"jsonb"
|
|
359
|
+
];
|
|
360
|
+
var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
|
|
361
|
+
var canonicalEnumSchema = zod.z.object({
|
|
362
|
+
name: identifier,
|
|
363
|
+
values: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(100)
|
|
364
|
+
});
|
|
365
|
+
var canonicalColumnSchema = zod.z.object({
|
|
366
|
+
/** Property name in TS (camelCase); columnName is the Postgres name. */
|
|
367
|
+
name: zod.z.string().min(1).max(63),
|
|
368
|
+
columnName: identifier,
|
|
369
|
+
type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier })]),
|
|
370
|
+
nullable: zod.z.boolean().default(true),
|
|
371
|
+
unique: zod.z.boolean().default(false),
|
|
372
|
+
/** Literal default, or one of the allowlisted SQL functions. */
|
|
373
|
+
default: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]).optional(),
|
|
374
|
+
defaultFn: zod.z.enum(["gen_random_uuid()", "now()"]).optional(),
|
|
375
|
+
description: zod.z.string().max(500).optional(),
|
|
376
|
+
/** Migration hints — same semantics as v1. */
|
|
377
|
+
renamedFrom: identifier.optional(),
|
|
378
|
+
cast: zod.z.string().max(200).optional(),
|
|
379
|
+
deprecated: zod.z.boolean().default(false)
|
|
380
|
+
});
|
|
381
|
+
var canonicalLinkSchema = zod.z.object({
|
|
382
|
+
name: zod.z.string().min(1).max(63),
|
|
383
|
+
kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
|
|
384
|
+
/** Target table name (namespace-local). Absent for toPlatformUser. */
|
|
385
|
+
target: identifier.optional(),
|
|
386
|
+
/** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
|
|
387
|
+
on: identifier,
|
|
388
|
+
/** Referenced column on the target (default: target's PK / platform user `sub`). */
|
|
389
|
+
references: identifier.optional(),
|
|
390
|
+
/** enforce: true emits a real FK constraint (same-namespace toOne only). */
|
|
391
|
+
enforce: zod.z.boolean().default(false),
|
|
392
|
+
description: zod.z.string().max(500).optional()
|
|
393
|
+
});
|
|
394
|
+
var canonicalIndexSchema = zod.z.object({
|
|
395
|
+
columns: zod.z.array(identifier).min(1),
|
|
396
|
+
unique: zod.z.boolean().default(false),
|
|
397
|
+
name: identifier.optional()
|
|
398
|
+
});
|
|
399
|
+
var policyVariantSchema = zod.z.object({
|
|
400
|
+
/** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
|
|
401
|
+
when: zod.z.string().min(1).max(1e3),
|
|
402
|
+
/** Exactly one of: allow-all bypass, or a row predicate. */
|
|
403
|
+
allow: zod.z.literal(true).optional(),
|
|
404
|
+
policy: zod.z.string().min(1).max(2e3).optional()
|
|
405
|
+
}).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
|
|
406
|
+
message: "A variant declares exactly one of allow: true or policy"
|
|
407
|
+
});
|
|
408
|
+
var policyExpressionSchema = zod.z.union([
|
|
409
|
+
zod.z.string().min(1).max(2e3),
|
|
410
|
+
zod.z.object({
|
|
411
|
+
default: zod.z.string().min(1).max(2e3),
|
|
412
|
+
variants: zod.z.array(policyVariantSchema).max(10).default([])
|
|
413
|
+
})
|
|
414
|
+
]);
|
|
415
|
+
var canonicalPoliciesSchema = zod.z.object({
|
|
416
|
+
read: policyExpressionSchema.optional(),
|
|
417
|
+
write: policyExpressionSchema.optional()
|
|
418
|
+
});
|
|
419
|
+
var canonicalActionSchema = zod.z.object({
|
|
420
|
+
name: identifier,
|
|
421
|
+
requires: zod.z.string().max(1e3).optional(),
|
|
422
|
+
params: zod.z.record(zod.z.string(), zod.z.object({
|
|
423
|
+
type: columnTypeSchema,
|
|
424
|
+
nullable: zod.z.boolean().default(false)
|
|
425
|
+
})).default({}),
|
|
426
|
+
executes: zod.z.string().min(1).max(1e4),
|
|
427
|
+
returns: zod.z.enum(["rows", "rowsAffected", "scalar"]).default("rowsAffected"),
|
|
428
|
+
audit: zod.z.object({ action: zod.z.string(), resource: zod.z.string() }).optional()
|
|
429
|
+
});
|
|
430
|
+
var canonicalTableSchema = zod.z.object({
|
|
431
|
+
table: identifier,
|
|
432
|
+
description: zod.z.string().max(2e3).optional(),
|
|
433
|
+
/** Table-level rename hint (new in v2). */
|
|
434
|
+
renamedFrom: identifier.optional(),
|
|
435
|
+
/** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
|
|
436
|
+
source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
|
|
437
|
+
columns: zod.z.array(canonicalColumnSchema).default([]),
|
|
438
|
+
primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
|
|
439
|
+
links: zod.z.array(canonicalLinkSchema).default([]),
|
|
440
|
+
indexes: zod.z.array(canonicalIndexSchema).default([]),
|
|
441
|
+
policies: canonicalPoliciesSchema.default({}),
|
|
442
|
+
actions: zod.z.array(canonicalActionSchema).default([])
|
|
443
|
+
});
|
|
444
|
+
var canonicalDefinitionSchema = zod.z.object({
|
|
445
|
+
definition_format: zod.z.literal(CANONICAL_FORMAT),
|
|
446
|
+
namespace: identifier.max(41),
|
|
447
|
+
description: zod.z.string().max(2e3).optional(),
|
|
448
|
+
enums: zod.z.array(canonicalEnumSchema).default([]),
|
|
449
|
+
tables: zod.z.array(canonicalTableSchema).min(0).max(500),
|
|
450
|
+
/** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
|
|
451
|
+
capabilities: zod.z.object({
|
|
452
|
+
roles: zod.z.unknown().optional(),
|
|
453
|
+
uses: zod.z.array(zod.z.string()).default([])
|
|
454
|
+
}).default({ uses: [] })
|
|
455
|
+
});
|
|
456
|
+
function normalizeCanonical(definition) {
|
|
457
|
+
const sortByName = (items) => [...items].sort((a, b) => a.name.localeCompare(b.name));
|
|
458
|
+
return {
|
|
459
|
+
...definition,
|
|
460
|
+
enums: sortByName(definition.enums),
|
|
461
|
+
tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t2) => ({
|
|
462
|
+
...t2,
|
|
463
|
+
links: sortByName(t2.links),
|
|
464
|
+
indexes: [...t2.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
|
|
465
|
+
actions: sortByName(t2.actions)
|
|
466
|
+
}))
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function stableJson(value) {
|
|
470
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
471
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
472
|
+
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([a], [b]) => a < b ? -1 : 1).map(([key, entryValue]) => `${JSON.stringify(key)}:${stableJson(entryValue)}`);
|
|
473
|
+
return `{${entries.join(",")}}`;
|
|
474
|
+
}
|
|
475
|
+
function emptyToNull(value) {
|
|
476
|
+
if (value == null) return null;
|
|
477
|
+
if (Array.isArray(value)) return value.length === 0 ? null : value;
|
|
478
|
+
if (typeof value === "object" && Object.keys(value).length === 0) return null;
|
|
479
|
+
return value;
|
|
480
|
+
}
|
|
481
|
+
function canonicalTable(table) {
|
|
482
|
+
return {
|
|
483
|
+
table: table.table,
|
|
484
|
+
description: table.description ?? null,
|
|
485
|
+
columns: table.columns ?? null,
|
|
486
|
+
source: table.source ?? null,
|
|
487
|
+
links: emptyToNull(table.links),
|
|
488
|
+
indexes: emptyToNull(table.indexes),
|
|
489
|
+
policies: {
|
|
490
|
+
read: table.policies?.read?.source ?? null,
|
|
491
|
+
write: table.policies?.write?.source ?? null
|
|
492
|
+
},
|
|
493
|
+
actions: emptyToNull(
|
|
494
|
+
Object.fromEntries(
|
|
495
|
+
Object.entries(table.actions ?? {}).map(([name, action]) => [
|
|
496
|
+
name,
|
|
497
|
+
{
|
|
498
|
+
description: action.description ?? null,
|
|
499
|
+
requires: action.requires?.source ?? null,
|
|
500
|
+
params: emptyToNull(action.params),
|
|
501
|
+
validates: emptyToNull(action.validates?.map((entry) => entry.source)),
|
|
502
|
+
executes: action.executes,
|
|
503
|
+
returns: emptyToNull(action.returns),
|
|
504
|
+
audit: action.audit ?? null
|
|
505
|
+
}
|
|
506
|
+
])
|
|
507
|
+
)
|
|
508
|
+
)
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function canonicalCapabilities(capabilities) {
|
|
512
|
+
const { definitions, capabilities: declared } = capabilities.roles;
|
|
513
|
+
const isEmpty = Object.keys(definitions).length === 0 && Object.keys(declared).length === 0 && capabilities.uses.length === 0;
|
|
514
|
+
if (isEmpty) return null;
|
|
515
|
+
return {
|
|
516
|
+
roles: {
|
|
517
|
+
definitions: Object.fromEntries(
|
|
518
|
+
Object.entries(definitions).map(([key, def]) => [
|
|
519
|
+
key,
|
|
520
|
+
{
|
|
521
|
+
...def.description !== void 0 ? { description: def.description } : {},
|
|
522
|
+
default: def.default,
|
|
523
|
+
inherits: [...def.inherits].sort(),
|
|
524
|
+
grants: [...def.grants].sort()
|
|
525
|
+
}
|
|
526
|
+
])
|
|
527
|
+
),
|
|
528
|
+
capabilities: Object.fromEntries(
|
|
529
|
+
Object.entries(declared).map(([key, cap]) => [
|
|
530
|
+
key,
|
|
531
|
+
cap.description !== void 0 ? { description: cap.description } : {}
|
|
532
|
+
])
|
|
533
|
+
)
|
|
534
|
+
},
|
|
535
|
+
uses: [...capabilities.uses].sort()
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function canonicalizeDefinition(definition) {
|
|
539
|
+
return {
|
|
540
|
+
namespace: definition.namespace,
|
|
541
|
+
description: definition.description,
|
|
542
|
+
capabilities: canonicalCapabilities(definition.capabilities),
|
|
543
|
+
tables: definition.tables.map(canonicalTable)
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function computeDefinitionHash(definition) {
|
|
547
|
+
return crypto.createHash("sha256").update(HASH_DOMAIN).update(stableJson(canonicalizeDefinition(definition))).digest("hex");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/authoring/namespace.ts
|
|
551
|
+
var NAMESPACE_CONFIG_BRAND = "seq-namespace-config";
|
|
552
|
+
function isSeqNamespaceConfig(value) {
|
|
553
|
+
return typeof value === "object" && value !== null && value.kind === NAMESPACE_CONFIG_BRAND;
|
|
554
|
+
}
|
|
555
|
+
function defineNamespace(input) {
|
|
556
|
+
const file = activeAuthoringContext()?.currentFile;
|
|
557
|
+
if (!NAMESPACE_PATTERN.test(input.namespace)) {
|
|
558
|
+
throw new OrmDefinitionError(`namespace "${input.namespace}" must match ${NAMESPACE_PATTERN}`, file);
|
|
559
|
+
}
|
|
560
|
+
const config = {
|
|
561
|
+
kind: NAMESPACE_CONFIG_BRAND,
|
|
562
|
+
namespace: input.namespace,
|
|
563
|
+
description: input.description,
|
|
564
|
+
capabilities: input.capabilities ?? {},
|
|
565
|
+
file
|
|
566
|
+
};
|
|
567
|
+
registerNamespaceConfig(config);
|
|
568
|
+
return config;
|
|
569
|
+
}
|
|
570
|
+
function serializeNamespace(input) {
|
|
571
|
+
const { config, tables } = input;
|
|
572
|
+
const enums = input.enums ?? [];
|
|
573
|
+
const tablesByName = /* @__PURE__ */ new Map();
|
|
574
|
+
for (const table of tables) {
|
|
575
|
+
const existing = tablesByName.get(table.tableName);
|
|
576
|
+
if (existing) {
|
|
577
|
+
throw new OrmDefinitionError(
|
|
578
|
+
`duplicate table "${table.tableName}" (defined in ${describeFile(existing)} and ${describeFile(table)})`,
|
|
579
|
+
table.file
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
tablesByName.set(table.tableName, table);
|
|
583
|
+
}
|
|
584
|
+
const enumsByName = /* @__PURE__ */ new Map();
|
|
585
|
+
for (const ref of enums) {
|
|
586
|
+
if (enumsByName.has(ref.name)) {
|
|
587
|
+
throw new OrmDefinitionError(`duplicate enum "${ref.name}"`, config.file);
|
|
588
|
+
}
|
|
589
|
+
enumsByName.set(ref.name, ref);
|
|
590
|
+
}
|
|
591
|
+
const canonicalTables = tables.map((table) => buildCanonicalTable({ table, tablesByName, enumsByName }));
|
|
592
|
+
const canonicalEnums = [...enumsByName.values()].map((ref) => ({
|
|
593
|
+
name: ref.name,
|
|
594
|
+
values: [...ref.values]
|
|
595
|
+
}));
|
|
596
|
+
const candidate = {
|
|
597
|
+
definition_format: CANONICAL_FORMAT,
|
|
598
|
+
namespace: config.namespace,
|
|
599
|
+
enums: canonicalEnums,
|
|
600
|
+
tables: canonicalTables,
|
|
601
|
+
capabilities: {
|
|
602
|
+
...config.capabilities.roles !== void 0 ? { roles: config.capabilities.roles } : {},
|
|
603
|
+
uses: [...config.capabilities.uses ?? []]
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
if (config.description !== void 0) candidate.description = config.description;
|
|
607
|
+
let parsed;
|
|
608
|
+
try {
|
|
609
|
+
parsed = canonicalDefinitionSchema.parse(candidate);
|
|
610
|
+
} catch (error) {
|
|
611
|
+
if (error instanceof zod.z.ZodError) {
|
|
612
|
+
throw new OrmDefinitionError(`namespace "${config.namespace}": ${formatZodIssues(error)}`, config.file);
|
|
613
|
+
}
|
|
614
|
+
throw error;
|
|
615
|
+
}
|
|
616
|
+
return normalizeCanonical(parsed);
|
|
617
|
+
}
|
|
618
|
+
function computeCanonicalHash(definition) {
|
|
619
|
+
return crypto.createHash("sha256").update(HASH_DOMAIN).update(stableJson(definition)).digest("hex");
|
|
620
|
+
}
|
|
621
|
+
function buildCanonicalTable({ table, tablesByName, enumsByName }) {
|
|
622
|
+
const candidate = {
|
|
623
|
+
table: table.tableName,
|
|
624
|
+
columns: [],
|
|
625
|
+
links: [],
|
|
626
|
+
indexes: [],
|
|
627
|
+
policies: {},
|
|
628
|
+
actions: []
|
|
629
|
+
};
|
|
630
|
+
if (table.config.description !== void 0) candidate.description = table.config.description;
|
|
631
|
+
if (table.config.renamedFrom !== void 0) candidate.renamedFrom = table.config.renamedFrom;
|
|
632
|
+
if (table.source !== void 0) candidate.source = table.source;
|
|
633
|
+
const columnLevelPk = [];
|
|
634
|
+
for (const [propertyName, builder] of Object.entries(table.columns)) {
|
|
635
|
+
const column = builder.toCanonicalColumn(propertyName);
|
|
636
|
+
const enumName = builder.enumName;
|
|
637
|
+
if (enumName !== void 0 && !enumsByName.has(enumName)) {
|
|
638
|
+
throw new OrmDefinitionError(
|
|
639
|
+
`table "${table.tableName}": column "${propertyName}" references enum "${enumName}" which is not passed to serializeNamespace \u2014 declare it with defineEnum and include it`,
|
|
640
|
+
table.file
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if (builder.isPrimaryKey) columnLevelPk.push(column.columnName);
|
|
644
|
+
candidate.columns?.push(column);
|
|
645
|
+
}
|
|
646
|
+
if (table.config.primaryKey !== void 0) {
|
|
647
|
+
if (columnLevelPk.length > 0) {
|
|
648
|
+
throw new OrmDefinitionError(
|
|
649
|
+
`table "${table.tableName}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
|
|
650
|
+
table.file
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
candidate.primaryKey = table.config.primaryKey.map(
|
|
654
|
+
(ref) => resolveColumnRef({ table, ref, role: "primaryKey" })
|
|
655
|
+
);
|
|
656
|
+
} else if (columnLevelPk.length > 0) {
|
|
657
|
+
candidate.primaryKey = columnLevelPk;
|
|
658
|
+
}
|
|
659
|
+
for (const index3 of table.config.indexes ?? []) {
|
|
660
|
+
const entry = {
|
|
661
|
+
columns: index3.columns.map((ref) => resolveColumnRef({ table, ref, role: "index" }))
|
|
662
|
+
};
|
|
663
|
+
if (index3.unique !== void 0) entry.unique = index3.unique;
|
|
664
|
+
if (index3.name !== void 0) entry.name = index3.name;
|
|
665
|
+
candidate.indexes?.push(entry);
|
|
666
|
+
}
|
|
667
|
+
for (const [linkName, link] of Object.entries(table.config.links ?? {})) {
|
|
668
|
+
candidate.links?.push(buildCanonicalLink({ table, linkName, link, tablesByName }));
|
|
669
|
+
}
|
|
670
|
+
const policies = {};
|
|
671
|
+
if (table.config.policies?.read !== void 0) policies.read = toCanonicalPolicy(table.config.policies.read);
|
|
672
|
+
if (table.config.policies?.write !== void 0) policies.write = toCanonicalPolicy(table.config.policies.write);
|
|
673
|
+
candidate.policies = policies;
|
|
674
|
+
for (const action of table.config.actions ?? []) {
|
|
675
|
+
candidate.actions?.push({
|
|
676
|
+
name: action.name,
|
|
677
|
+
...action.requires !== void 0 ? { requires: action.requires } : {},
|
|
678
|
+
params: Object.fromEntries(
|
|
679
|
+
Object.entries(action.params ?? {}).map(([name, param]) => [
|
|
680
|
+
name,
|
|
681
|
+
{ type: param.type, nullable: param.nullable ?? false }
|
|
682
|
+
])
|
|
683
|
+
),
|
|
684
|
+
executes: action.executes,
|
|
685
|
+
returns: action.returns ?? "rowsAffected",
|
|
686
|
+
...action.audit !== void 0 ? { audit: action.audit } : {}
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
try {
|
|
690
|
+
return canonicalTableSchema.parse(candidate);
|
|
691
|
+
} catch (error) {
|
|
692
|
+
if (error instanceof zod.z.ZodError) {
|
|
693
|
+
throw new OrmDefinitionError(`table "${table.tableName}": ${formatZodIssues(error)}`, table.file);
|
|
694
|
+
}
|
|
695
|
+
throw error;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function buildCanonicalLink({
|
|
699
|
+
table,
|
|
700
|
+
linkName,
|
|
701
|
+
link,
|
|
702
|
+
tablesByName
|
|
703
|
+
}) {
|
|
704
|
+
const fail = (message) => {
|
|
705
|
+
throw new OrmDefinitionError(`table "${table.tableName}": link "${linkName}" ${message}`, table.file);
|
|
706
|
+
};
|
|
707
|
+
if (link.enforce && link.linkKind !== "toOne") {
|
|
708
|
+
return fail("sets enforce: true, which is only supported on toOne links");
|
|
709
|
+
}
|
|
710
|
+
if (link.linkKind === "toPlatformUser") {
|
|
711
|
+
if (link.target !== void 0) return fail("is toPlatformUser and must not declare a target table");
|
|
712
|
+
return {
|
|
713
|
+
name: linkName,
|
|
714
|
+
kind: "toPlatformUser",
|
|
715
|
+
on: resolveColumnRef({ table, ref: link.on, role: `link "${linkName}" on` }),
|
|
716
|
+
...link.references !== void 0 ? { references: deriveColumnName(link.references) } : {},
|
|
717
|
+
enforce: false,
|
|
718
|
+
...link.description !== void 0 ? { description: link.description } : {}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
const target = resolveTableRef({ ref: link.target, fail });
|
|
722
|
+
if (!tablesByName.has(target.tableName)) {
|
|
723
|
+
return fail(`targets table "${target.tableName}" which is not part of this namespace`);
|
|
724
|
+
}
|
|
725
|
+
const owningSide = link.linkKind === "toOne" ? table : target;
|
|
726
|
+
return {
|
|
727
|
+
name: linkName,
|
|
728
|
+
kind: link.linkKind,
|
|
729
|
+
target: target.tableName,
|
|
730
|
+
on: resolveColumnRef({ table: owningSide, ref: link.on, role: `link "${linkName}" on` }),
|
|
731
|
+
...link.references !== void 0 ? { references: resolveColumnRef({ table: target, ref: link.references, role: `link "${linkName}" references` }) } : {},
|
|
732
|
+
enforce: link.enforce,
|
|
733
|
+
...link.description !== void 0 ? { description: link.description } : {}
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function resolveTableRef({ ref, fail }) {
|
|
737
|
+
if (ref === void 0) return fail("declares no target table");
|
|
738
|
+
if (typeof ref !== "function") return ref;
|
|
739
|
+
let resolved;
|
|
740
|
+
try {
|
|
741
|
+
resolved = ref();
|
|
742
|
+
} catch (error) {
|
|
743
|
+
return fail(
|
|
744
|
+
`target thunk threw (${error instanceof Error ? error.message : String(error)}) \u2014 lazy refs resolve at serialize time; make sure the target module ran`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
if (!isSeqTable(resolved)) return fail("target thunk did not return a table defined with defineTable");
|
|
748
|
+
return resolved;
|
|
749
|
+
}
|
|
750
|
+
function resolveColumnRef({ table, ref, role }) {
|
|
751
|
+
const direct = table.columns[ref];
|
|
752
|
+
if (direct) return direct.columnNameFor(ref);
|
|
753
|
+
for (const [propertyName, builder] of Object.entries(table.columns)) {
|
|
754
|
+
if (builder.columnNameFor(propertyName) === ref) return ref;
|
|
755
|
+
}
|
|
756
|
+
const known = Object.keys(table.columns).join(", ");
|
|
757
|
+
throw new OrmDefinitionError(
|
|
758
|
+
`table "${table.tableName}": ${role} references unknown column "${ref}" (known columns: ${known || "none"})`,
|
|
759
|
+
table.file
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
function toCanonicalPolicy(policy) {
|
|
763
|
+
if (typeof policy === "string") return policy;
|
|
764
|
+
return {
|
|
765
|
+
default: policy.default,
|
|
766
|
+
variants: (policy.variants ?? []).map((variant) => ({
|
|
767
|
+
when: variant.when,
|
|
768
|
+
...variant.allow === true ? { allow: true } : {},
|
|
769
|
+
...variant.policy !== void 0 ? { policy: variant.policy } : {}
|
|
770
|
+
}))
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function describeFile(table) {
|
|
774
|
+
return table.file ?? "<unknown file>";
|
|
775
|
+
}
|
|
776
|
+
function formatZodIssues(error) {
|
|
777
|
+
return error.issues.map((issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message).join("; ");
|
|
778
|
+
}
|
|
779
|
+
var NAMESPACE_CONFIG_FILE = "sequence.config.ts";
|
|
780
|
+
var SCHEMA_DIR = "schema";
|
|
781
|
+
async function loadNamespaceFromDir(dir, options = {}) {
|
|
782
|
+
const root = await promises.realpath(dir).catch(() => {
|
|
783
|
+
throw new OrmDefinitionError(`namespace directory not found: ${dir}`);
|
|
784
|
+
});
|
|
785
|
+
const configPath = path.join(root, NAMESPACE_CONFIG_FILE);
|
|
786
|
+
const configStat = await promises.stat(configPath).catch(() => void 0);
|
|
787
|
+
if (!configStat?.isFile()) {
|
|
788
|
+
throw new OrmDefinitionError(`${NAMESPACE_CONFIG_FILE} not found in ${dir}`);
|
|
789
|
+
}
|
|
790
|
+
const schemaFiles = await listSchemaFiles(root);
|
|
791
|
+
const files = [NAMESPACE_CONFIG_FILE, ...schemaFiles];
|
|
792
|
+
purgeModuleCache(root);
|
|
793
|
+
const jiti$1 = jiti.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)), {
|
|
794
|
+
interopDefault: true,
|
|
795
|
+
fsCache: false,
|
|
796
|
+
...options.ormModulePath !== void 0 ? { alias: { "@sequenceholdings/orm": options.ormModulePath } } : {}
|
|
797
|
+
});
|
|
798
|
+
const context = beginAuthoringContext();
|
|
799
|
+
try {
|
|
800
|
+
for (const file of files) {
|
|
801
|
+
context.currentFile = file;
|
|
802
|
+
try {
|
|
803
|
+
await jiti$1.import(path.join(root, file));
|
|
804
|
+
} catch (error) {
|
|
805
|
+
if (error instanceof Error && error.name === "OrmDefinitionError") throw error;
|
|
806
|
+
throw new OrmDefinitionError(
|
|
807
|
+
`failed to load: ${error instanceof Error ? error.message : String(error)}`,
|
|
808
|
+
file
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
} finally {
|
|
813
|
+
context.currentFile = void 0;
|
|
814
|
+
endAuthoringContext(context);
|
|
815
|
+
}
|
|
816
|
+
const configs = [];
|
|
817
|
+
for (const entry of context.configs) {
|
|
818
|
+
if (isSeqNamespaceConfig(entry.value)) configs.push({ value: entry.value, file: entry.file });
|
|
819
|
+
}
|
|
820
|
+
const configEntry = configs[0];
|
|
821
|
+
if (!configEntry) {
|
|
822
|
+
throw new OrmDefinitionError(
|
|
823
|
+
"no namespace config registered \u2014 sequence.config.ts must call defineNamespace(...) at import time",
|
|
824
|
+
NAMESPACE_CONFIG_FILE
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
if (configs.length > 1) {
|
|
828
|
+
const where = configs.map((entry) => entry.file ?? "<unknown file>").join(", ");
|
|
829
|
+
throw new OrmDefinitionError(
|
|
830
|
+
`defineNamespace called ${configs.length} times (${where}) \u2014 declare exactly one namespace config`
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
const tables = [];
|
|
834
|
+
for (const entry of context.tables) {
|
|
835
|
+
if (isSeqTable(entry.value)) tables.push(entry.value);
|
|
836
|
+
}
|
|
837
|
+
if (tables.length === 0) {
|
|
838
|
+
throw new OrmDefinitionError(
|
|
839
|
+
`no tables registered \u2014 ${SCHEMA_DIR}/*.ts modules must call defineTable(...) at import time`
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
const enums = [];
|
|
843
|
+
for (const entry of context.enums) {
|
|
844
|
+
if (isEnumRef(entry.value)) enums.push(entry.value);
|
|
845
|
+
}
|
|
846
|
+
const canonical = serializeNamespace({ config: configEntry.value, tables, enums });
|
|
847
|
+
return { canonical, hash: computeCanonicalHash(canonical), files };
|
|
848
|
+
}
|
|
849
|
+
async function listSchemaFiles(root) {
|
|
850
|
+
const schemaDir = path.join(root, SCHEMA_DIR);
|
|
851
|
+
const entries = await promises.readdir(schemaDir, { withFileTypes: true }).catch(() => []);
|
|
852
|
+
return entries.filter(
|
|
853
|
+
(entry) => entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts") && !entry.name.startsWith(".")
|
|
854
|
+
).map((entry) => path.join(SCHEMA_DIR, entry.name)).sort();
|
|
855
|
+
}
|
|
856
|
+
function purgeModuleCache(root) {
|
|
857
|
+
const require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
858
|
+
const prefix = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
|
|
859
|
+
for (const key of Object.keys(require2.cache)) {
|
|
860
|
+
if (key.startsWith(prefix)) delete require2.cache[key];
|
|
861
|
+
}
|
|
862
|
+
}
|
|
11
863
|
|
|
12
864
|
// src/codegen/types.ts
|
|
13
865
|
var WIRE_TYPES = {
|
|
@@ -24,7 +876,7 @@ var WIRE_TYPES = {
|
|
|
24
876
|
};
|
|
25
877
|
function generateNamespaceTypes(definition) {
|
|
26
878
|
const lines = [
|
|
27
|
-
`// Generated by \`seq-studio orm
|
|
879
|
+
`// Generated by \`seq-studio orm apply\` from the '${definition.namespace}' definitions \u2014 do not edit.`,
|
|
28
880
|
"",
|
|
29
881
|
`export type ${typesInterfaceName(definition.namespace)} = {`
|
|
30
882
|
];
|
|
@@ -86,43 +938,6 @@ function columnWireType(column) {
|
|
|
86
938
|
return notNull || base === "unknown" ? base : `${base} | null`;
|
|
87
939
|
}
|
|
88
940
|
|
|
89
|
-
// src/constants.ts
|
|
90
|
-
var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
|
|
91
|
-
var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
92
|
-
var MAX_IDENTIFIER_LENGTH = 63;
|
|
93
|
-
var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
|
|
94
|
-
var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
|
|
95
|
-
var CAPABILITY_KEY_PATTERN = /^(?:org\/)?[a-z0-9-]{1,64}$/;
|
|
96
|
-
var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
97
|
-
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/;
|
|
98
|
-
var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
|
|
99
|
-
var ORM_SCHEMA_PREFIX = "orm_";
|
|
100
|
-
var CONSUMER_ROLE_SUFFIX = "_consumer";
|
|
101
|
-
var ormSchemaName = (namespace) => `${ORM_SCHEMA_PREFIX}${namespace}`;
|
|
102
|
-
var consumerRoleName = (namespace) => `${ORM_SCHEMA_PREFIX}${namespace}${CONSUMER_ROLE_SUFFIX}`;
|
|
103
|
-
var DEFINITION_FILE_EXTENSIONS = [".yml", ".yaml"];
|
|
104
|
-
var NAMESPACE_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["namespace.yaml", "namespace.yml"]);
|
|
105
|
-
var HASH_DOMAIN = "sequence-orm-definitions\0";
|
|
106
|
-
var MAX_DEFINITION_FILE_BYTES = 1024 * 1024;
|
|
107
|
-
var RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set(["actions", "query"]);
|
|
108
|
-
var RESERVED_COLUMN_NAMES = /* @__PURE__ */ new Set(["select", "order", "limit", "offset"]);
|
|
109
|
-
|
|
110
|
-
// src/errors.ts
|
|
111
|
-
var OrmDefinitionError = class extends Error {
|
|
112
|
-
constructor(message, file) {
|
|
113
|
-
super(file ? `${file}: ${message}` : message);
|
|
114
|
-
this.file = file;
|
|
115
|
-
}
|
|
116
|
-
name = "OrmDefinitionError";
|
|
117
|
-
};
|
|
118
|
-
var PredicateParseError = class extends Error {
|
|
119
|
-
constructor(message, position) {
|
|
120
|
-
super(`${message} (at position ${position})`);
|
|
121
|
-
this.position = position;
|
|
122
|
-
}
|
|
123
|
-
name = "PredicateParseError";
|
|
124
|
-
};
|
|
125
|
-
|
|
126
941
|
// src/predicate/ast.ts
|
|
127
942
|
var COMPARE_OPS = ["=", "!=", "<", "<=", ">", ">="];
|
|
128
943
|
function walkPredicate(predicate, visit) {
|
|
@@ -161,7 +976,7 @@ var tableReferenceSchema = zod.z.string().refine(
|
|
|
161
976
|
var isQualifiedReference = (reference) => reference.includes(".");
|
|
162
977
|
|
|
163
978
|
// src/schema/columns.ts
|
|
164
|
-
var
|
|
979
|
+
var COLUMN_TYPES2 = [
|
|
165
980
|
"uuid",
|
|
166
981
|
"text",
|
|
167
982
|
"integer",
|
|
@@ -173,7 +988,7 @@ var COLUMN_TYPES = [
|
|
|
173
988
|
"date",
|
|
174
989
|
"jsonb"
|
|
175
990
|
];
|
|
176
|
-
var
|
|
991
|
+
var columnTypeSchema2 = zod.z.enum(COLUMN_TYPES2);
|
|
177
992
|
var FUNCTION_DEFAULTS = {
|
|
178
993
|
"gen_random_uuid()": ["uuid"],
|
|
179
994
|
"now()": ["timestamptz", "date"]
|
|
@@ -202,7 +1017,7 @@ function defaultIssue(value, type) {
|
|
|
202
1017
|
return null;
|
|
203
1018
|
}
|
|
204
1019
|
var columnSchema = zod.z.strictObject({
|
|
205
|
-
type:
|
|
1020
|
+
type: columnTypeSchema2,
|
|
206
1021
|
primary_key: zod.z.boolean().optional(),
|
|
207
1022
|
nullable: zod.z.boolean().optional(),
|
|
208
1023
|
unique: zod.z.boolean().optional(),
|
|
@@ -469,7 +1284,7 @@ var NAMED_PARAM_PATTERN = /(?<!:):([a-z][a-z0-9_]*)/g;
|
|
|
469
1284
|
var QUOTED_STRING_PATTERN = /'(?:[^']|'')*'/g;
|
|
470
1285
|
var SQL_COMMENT_PATTERN = /--|\/\*/;
|
|
471
1286
|
var actionParamSchema = zod.z.strictObject({
|
|
472
|
-
type:
|
|
1287
|
+
type: columnTypeSchema2,
|
|
473
1288
|
optional: zod.z.boolean().optional(),
|
|
474
1289
|
description: zod.z.string().optional()
|
|
475
1290
|
});
|
|
@@ -502,7 +1317,7 @@ var actionSchema = zod.z.strictObject({
|
|
|
502
1317
|
// function returns those rows (typed in the client); absent → the action
|
|
503
1318
|
// reports rows_affected only. The column names/types must match the
|
|
504
1319
|
// RETURNING list, which Postgres enforces at CREATE FUNCTION.
|
|
505
|
-
returns: zod.z.record(identifierSchema,
|
|
1320
|
+
returns: zod.z.record(identifierSchema, columnTypeSchema2).optional(),
|
|
506
1321
|
audit: auditSchema.optional()
|
|
507
1322
|
}).superRefine((action, ctx) => {
|
|
508
1323
|
const params = Object.keys(action.params ?? {});
|
|
@@ -519,30 +1334,53 @@ var actionSchema = zod.z.strictObject({
|
|
|
519
1334
|
if (action.returns && Object.keys(action.returns).length === 0) {
|
|
520
1335
|
ctx.addIssue({ code: "custom", message: "returns must declare at least one column", path: ["returns"] });
|
|
521
1336
|
}
|
|
522
|
-
for (const [
|
|
1337
|
+
for (const [index3, entry] of (action.validates ?? []).entries()) {
|
|
523
1338
|
if (!params.includes(entry.param)) {
|
|
524
1339
|
ctx.addIssue({
|
|
525
1340
|
code: "custom",
|
|
526
1341
|
message: `validates references undeclared param '${entry.param}'`,
|
|
527
|
-
path: ["validates",
|
|
1342
|
+
path: ["validates", index3]
|
|
528
1343
|
});
|
|
529
1344
|
}
|
|
530
1345
|
}
|
|
531
1346
|
});
|
|
532
|
-
function extractNamedParams(
|
|
533
|
-
const withoutStrings =
|
|
1347
|
+
function extractNamedParams(sql3) {
|
|
1348
|
+
const withoutStrings = sql3.replace(QUOTED_STRING_PATTERN, "''");
|
|
534
1349
|
const names = [...withoutStrings.matchAll(NAMED_PARAM_PATTERN)].map((match) => match[1]);
|
|
535
1350
|
return [...new Set(names)];
|
|
536
1351
|
}
|
|
537
1352
|
function actionStatementSources(executes) {
|
|
538
1353
|
return (Array.isArray(executes) ? executes : [executes]).map((statement) => statement.trim());
|
|
539
1354
|
}
|
|
1355
|
+
var WRITE_TARGET_PATTERNS = [
|
|
1356
|
+
/^insert\s+into\s+(?:"?[a-z_][a-z0-9_]*"?\.)?"?([a-z_][a-z0-9_]*)"?/i,
|
|
1357
|
+
/^update\s+(?:only\s+)?(?:"?[a-z_][a-z0-9_]*"?\.)?"?([a-z_][a-z0-9_]*)"?/i,
|
|
1358
|
+
/^delete\s+from\s+(?:only\s+)?(?:"?[a-z_][a-z0-9_]*"?\.)?"?([a-z_][a-z0-9_]*)"?/i
|
|
1359
|
+
];
|
|
1360
|
+
function actionWriteTargets(action) {
|
|
1361
|
+
const targets = [];
|
|
1362
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1363
|
+
for (const statement of actionStatementSources(action.executes)) {
|
|
1364
|
+
const masked = statement.replace(QUOTED_STRING_PATTERN, "''");
|
|
1365
|
+
for (const pattern of WRITE_TARGET_PATTERNS) {
|
|
1366
|
+
const match = pattern.exec(masked);
|
|
1367
|
+
if (!match) continue;
|
|
1368
|
+
const table = match[1].toLowerCase();
|
|
1369
|
+
if (!seen.has(table)) {
|
|
1370
|
+
seen.add(table);
|
|
1371
|
+
targets.push(table);
|
|
1372
|
+
}
|
|
1373
|
+
break;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
return targets;
|
|
1377
|
+
}
|
|
540
1378
|
function executesIssues(executes, declaredParams, hasReturns) {
|
|
541
1379
|
const issues = [];
|
|
542
1380
|
const statements = actionStatementSources(executes);
|
|
543
|
-
statements.forEach((statement,
|
|
1381
|
+
statements.forEach((statement, index3) => {
|
|
544
1382
|
const withoutStrings = statement.replace(QUOTED_STRING_PATTERN, "''");
|
|
545
|
-
const at = statements.length > 1 ? ` (statement ${
|
|
1383
|
+
const at = statements.length > 1 ? ` (statement ${index3 + 1})` : "";
|
|
546
1384
|
if (!EXECUTES_VERB_PATTERN.test(statement)) issues.push(`executes must be INSERT, UPDATE, or DELETE${at}`);
|
|
547
1385
|
if (withoutStrings.replace(/;\s*$/, "").includes(";")) issues.push(`each executes entry must be a single statement${at}`);
|
|
548
1386
|
if (SQL_COMMENT_PATTERN.test(withoutStrings)) issues.push(`executes must not contain SQL comments${at}`);
|
|
@@ -552,7 +1390,7 @@ function executesIssues(executes, declaredParams, hasReturns) {
|
|
|
552
1390
|
if (BARE_CLAIM_CALL_PATTERN.test(withoutStrings.replace(MASKED_CLAIM_CALL_PATTERN, ""))) {
|
|
553
1391
|
issues.push(`executes has a malformed claim()/capability() call \u2014 use e.g. claim('sub')${at}`);
|
|
554
1392
|
}
|
|
555
|
-
if (RETURNING_PATTERN.test(withoutStrings) && !(hasReturns &&
|
|
1393
|
+
if (RETURNING_PATTERN.test(withoutStrings) && !(hasReturns && index3 === statements.length - 1)) {
|
|
556
1394
|
issues.push(
|
|
557
1395
|
hasReturns ? `only the last executes statement may use RETURNING${at}` : `executes must not use RETURNING unless the action declares returns${at}`
|
|
558
1396
|
);
|
|
@@ -662,8 +1500,8 @@ var actionParamArg = (name) => `p_${name}`;
|
|
|
662
1500
|
function compileActionStatements(action, renderParam = actionParamArg) {
|
|
663
1501
|
return actionStatementSources(action.executes).map((statement) => compileStatement(statement, renderParam));
|
|
664
1502
|
}
|
|
665
|
-
function compileStatement(
|
|
666
|
-
const compiled =
|
|
1503
|
+
function compileStatement(sql3, renderParam) {
|
|
1504
|
+
const compiled = sql3.replace(TOKEN_PATTERN, (token, claimKey, capabilityKey, paramName) => {
|
|
667
1505
|
if (claimKey !== void 0) {
|
|
668
1506
|
if (!CLAIM_KEY_PATTERN.test(claimKey)) throw new OrmDefinitionError(`invalid claim key in executes: ${token}`);
|
|
669
1507
|
return claimExpression(claimKey);
|
|
@@ -716,9 +1554,9 @@ function actionPrepareChecks(definition) {
|
|
|
716
1554
|
const order = actionParamOrder(action);
|
|
717
1555
|
const signature = order.length > 0 ? ` (${order.map((param) => sqlTypeName(action.params[param].type)).join(", ")})` : "";
|
|
718
1556
|
const compiled = compileActionStatements(action, (param) => `$${order.indexOf(param) + 1}`);
|
|
719
|
-
compiled.forEach((
|
|
720
|
-
const checked = action.returns &&
|
|
721
|
-
statements.push(`PREPARE ${quoteIdentifier(`orm_check_${name}_${
|
|
1557
|
+
compiled.forEach((sql3, index3) => {
|
|
1558
|
+
const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns, sql3) : sql3;
|
|
1559
|
+
statements.push(`PREPARE ${quoteIdentifier(`orm_check_${name}_${index3}`)}${signature} AS ${checked}`);
|
|
722
1560
|
});
|
|
723
1561
|
}
|
|
724
1562
|
return statements;
|
|
@@ -833,7 +1671,7 @@ var indexSchema = zod.z.union([
|
|
|
833
1671
|
unique: zod.z.boolean().optional(),
|
|
834
1672
|
name: identifierSchema.optional()
|
|
835
1673
|
})
|
|
836
|
-
]).transform((
|
|
1674
|
+
]).transform((index3) => Array.isArray(index3) ? { columns: index3 } : index3);
|
|
837
1675
|
var policiesSchema = zod.z.strictObject({
|
|
838
1676
|
read: parsedPredicateSchema.optional(),
|
|
839
1677
|
write: parsedPredicateSchema.optional()
|
|
@@ -937,15 +1775,15 @@ function validateColumnTable(table, ctx) {
|
|
|
937
1775
|
});
|
|
938
1776
|
}
|
|
939
1777
|
}
|
|
940
|
-
for (const [position,
|
|
941
|
-
if (new Set(
|
|
1778
|
+
for (const [position, index3] of (table.indexes ?? []).entries()) {
|
|
1779
|
+
if (new Set(index3.columns).size !== index3.columns.length) {
|
|
942
1780
|
ctx.addIssue({
|
|
943
1781
|
code: "custom",
|
|
944
1782
|
message: "index names a column more than once",
|
|
945
1783
|
path: ["indexes", position]
|
|
946
1784
|
});
|
|
947
1785
|
}
|
|
948
|
-
for (const column of
|
|
1786
|
+
for (const column of index3.columns) {
|
|
949
1787
|
if (!names.includes(column)) {
|
|
950
1788
|
ctx.addIssue({
|
|
951
1789
|
code: "custom",
|
|
@@ -987,8 +1825,8 @@ function validateDerivedNameLengths(table, ctx) {
|
|
|
987
1825
|
path: ["table"]
|
|
988
1826
|
});
|
|
989
1827
|
}
|
|
990
|
-
for (const [position,
|
|
991
|
-
const name = indexName(table.table,
|
|
1828
|
+
for (const [position, index3] of (table.indexes ?? []).entries()) {
|
|
1829
|
+
const name = indexName(table.table, index3);
|
|
992
1830
|
if (name.length > MAX_IDENTIFIER_LENGTH) {
|
|
993
1831
|
ctx.addIssue({
|
|
994
1832
|
code: "custom",
|
|
@@ -1228,9 +2066,9 @@ function sortByEnforcedLinks(tables) {
|
|
|
1228
2066
|
var enforcedTargets = (table) => Object.values(table.links ?? {}).filter((link) => link.enforce && !isQualifiedReference(link.to) && link.to !== table.table).map((link) => link.to);
|
|
1229
2067
|
|
|
1230
2068
|
// src/engine/snapshot.ts
|
|
1231
|
-
function generateSnapshot(definition,
|
|
2069
|
+
function generateSnapshot(definition, previousId3) {
|
|
1232
2070
|
const { imports, schemaName } = buildDrizzleEntities(definition);
|
|
1233
|
-
return api.generateDrizzleJson(imports,
|
|
2071
|
+
return api.generateDrizzleJson(imports, previousId3, [schemaName]);
|
|
1234
2072
|
}
|
|
1235
2073
|
var emptySnapshot = (namespace) => api.generateDrizzleJson({}, void 0, [ormSchemaName(namespace)]);
|
|
1236
2074
|
var CURRENT_SNAPSHOT_VERSION = api.generateDrizzleJson({}).version;
|
|
@@ -1387,259 +2225,591 @@ async function guardedRun(run) {
|
|
|
1387
2225
|
var STATEMENT_KINDS = ["hint", "ddl", "security", "function"];
|
|
1388
2226
|
async function planMigration({
|
|
1389
2227
|
definition,
|
|
1390
|
-
previousSnapshot
|
|
1391
|
-
}) {
|
|
1392
|
-
const schema = ormSchemaName(definition.namespace);
|
|
1393
|
-
const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(definition.namespace);
|
|
1394
|
-
const hints = applyMigrationHints(previous, definition);
|
|
1395
|
-
const snapshot = generateSnapshot(definition, previousId(previous));
|
|
1396
|
-
const ddl = await diffStatements(hints.adjusted, snapshot);
|
|
1397
|
-
const policyDrops = policyDropStatements(schema, snapshotTableNames(previous, schema));
|
|
1398
|
-
const statements = [
|
|
1399
|
-
...policyDrops.map((
|
|
1400
|
-
...hints.statements.map((
|
|
1401
|
-
...ddl.map((
|
|
1402
|
-
...securityStatements(definition).map((
|
|
1403
|
-
// PREPARE each action's executes first (plpgsql bodies don't check columns
|
|
1404
|
-
// at CREATE), then create the functions. Both reference the tables the DDL
|
|
1405
|
-
// above creates, so they close the plan.
|
|
1406
|
-
...actionPrepareChecks(definition).map((
|
|
1407
|
-
...actionFunctionStatements(definition).map((
|
|
1408
|
-
];
|
|
1409
|
-
return { namespace: definition.namespace, schema, statements, snapshot };
|
|
1410
|
-
}
|
|
1411
|
-
var compileNamespace = (definition) => planMigration({ definition });
|
|
1412
|
-
var previousId = (snapshot) => snapshot.id;
|
|
1413
|
-
function stableJson(value) {
|
|
1414
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
1415
|
-
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
1416
|
-
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([a], [b]) => a < b ? -1 : 1).map(([key, entryValue]) => `${JSON.stringify(key)}:${stableJson(entryValue)}`);
|
|
1417
|
-
return `{${entries.join(",")}}`;
|
|
1418
|
-
}
|
|
1419
|
-
function emptyToNull(value) {
|
|
1420
|
-
if (value == null) return null;
|
|
1421
|
-
if (Array.isArray(value)) return value.length === 0 ? null : value;
|
|
1422
|
-
if (typeof value === "object" && Object.keys(value).length === 0) return null;
|
|
1423
|
-
return value;
|
|
1424
|
-
}
|
|
1425
|
-
function canonicalTable(table) {
|
|
1426
|
-
return {
|
|
1427
|
-
table: table.table,
|
|
1428
|
-
description: table.description ?? null,
|
|
1429
|
-
columns: table.columns ?? null,
|
|
1430
|
-
source: table.source ?? null,
|
|
1431
|
-
links: emptyToNull(table.links),
|
|
1432
|
-
indexes: emptyToNull(table.indexes),
|
|
1433
|
-
policies: {
|
|
1434
|
-
read: table.policies?.read?.source ?? null,
|
|
1435
|
-
write: table.policies?.write?.source ?? null
|
|
1436
|
-
},
|
|
1437
|
-
actions: emptyToNull(
|
|
1438
|
-
Object.fromEntries(
|
|
1439
|
-
Object.entries(table.actions ?? {}).map(([name, action]) => [
|
|
1440
|
-
name,
|
|
1441
|
-
{
|
|
1442
|
-
description: action.description ?? null,
|
|
1443
|
-
requires: action.requires?.source ?? null,
|
|
1444
|
-
params: emptyToNull(action.params),
|
|
1445
|
-
validates: emptyToNull(action.validates?.map((entry) => entry.source)),
|
|
1446
|
-
executes: action.executes,
|
|
1447
|
-
returns: emptyToNull(action.returns),
|
|
1448
|
-
audit: action.audit ?? null
|
|
1449
|
-
}
|
|
1450
|
-
])
|
|
1451
|
-
)
|
|
1452
|
-
)
|
|
1453
|
-
};
|
|
1454
|
-
}
|
|
1455
|
-
function canonicalCapabilities(capabilities) {
|
|
1456
|
-
const { definitions, capabilities: declared } = capabilities.roles;
|
|
1457
|
-
const isEmpty = Object.keys(definitions).length === 0 && Object.keys(declared).length === 0 && capabilities.uses.length === 0;
|
|
1458
|
-
if (isEmpty) return null;
|
|
1459
|
-
return {
|
|
1460
|
-
roles: {
|
|
1461
|
-
definitions: Object.fromEntries(
|
|
1462
|
-
Object.entries(definitions).map(([key, def]) => [
|
|
1463
|
-
key,
|
|
1464
|
-
{
|
|
1465
|
-
...def.description !== void 0 ? { description: def.description } : {},
|
|
1466
|
-
default: def.default,
|
|
1467
|
-
inherits: [...def.inherits].sort(),
|
|
1468
|
-
grants: [...def.grants].sort()
|
|
1469
|
-
}
|
|
1470
|
-
])
|
|
1471
|
-
),
|
|
1472
|
-
capabilities: Object.fromEntries(
|
|
1473
|
-
Object.entries(declared).map(([key, cap]) => [
|
|
1474
|
-
key,
|
|
1475
|
-
cap.description !== void 0 ? { description: cap.description } : {}
|
|
1476
|
-
])
|
|
1477
|
-
)
|
|
1478
|
-
},
|
|
1479
|
-
uses: [...capabilities.uses].sort()
|
|
1480
|
-
};
|
|
2228
|
+
previousSnapshot
|
|
2229
|
+
}) {
|
|
2230
|
+
const schema = ormSchemaName(definition.namespace);
|
|
2231
|
+
const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(definition.namespace);
|
|
2232
|
+
const hints = applyMigrationHints(previous, definition);
|
|
2233
|
+
const snapshot = generateSnapshot(definition, previousId(previous));
|
|
2234
|
+
const ddl = await diffStatements(hints.adjusted, snapshot);
|
|
2235
|
+
const policyDrops = policyDropStatements(schema, snapshotTableNames(previous, schema));
|
|
2236
|
+
const statements = [
|
|
2237
|
+
...policyDrops.map((sql3) => ({ kind: "security", sql: sql3 })),
|
|
2238
|
+
...hints.statements.map((sql3) => ({ kind: "hint", sql: sql3 })),
|
|
2239
|
+
...ddl.map((sql3) => ({ kind: "ddl", sql: sql3.replace(/;\s*$/, "") })),
|
|
2240
|
+
...securityStatements(definition).map((sql3) => ({ kind: "security", sql: sql3 })),
|
|
2241
|
+
// PREPARE each action's executes first (plpgsql bodies don't check columns
|
|
2242
|
+
// at CREATE), then create the functions. Both reference the tables the DDL
|
|
2243
|
+
// above creates, so they close the plan.
|
|
2244
|
+
...actionPrepareChecks(definition).map((sql3) => ({ kind: "function", sql: sql3 })),
|
|
2245
|
+
...actionFunctionStatements(definition).map((sql3) => ({ kind: "function", sql: sql3 }))
|
|
2246
|
+
];
|
|
2247
|
+
return { namespace: definition.namespace, schema, statements, snapshot };
|
|
1481
2248
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
2249
|
+
var compileNamespace = (definition) => planMigration({ definition });
|
|
2250
|
+
var previousId = (snapshot) => snapshot.id;
|
|
2251
|
+
var FACTORIES2 = {
|
|
2252
|
+
uuid: (name) => pgCore.uuid(name),
|
|
2253
|
+
text: (name) => pgCore.text(name),
|
|
2254
|
+
integer: (name) => pgCore.integer(name),
|
|
2255
|
+
// Mode 'bigint' keeps int8 lossless — mode 'number' silently rounds past
|
|
2256
|
+
// 2^53. The read executor serializes BigInt to string for JSON.
|
|
2257
|
+
bigint: (name) => pgCore.bigint(name, { mode: "bigint" }),
|
|
2258
|
+
numeric: (name) => pgCore.numeric(name),
|
|
2259
|
+
double: (name) => pgCore.doublePrecision(name),
|
|
2260
|
+
boolean: (name) => pgCore.boolean(name),
|
|
2261
|
+
timestamptz: (name) => pgCore.timestamp(name, { withTimezone: true }),
|
|
2262
|
+
date: (name) => pgCore.date(name),
|
|
2263
|
+
jsonb: (name) => pgCore.jsonb(name)
|
|
2264
|
+
};
|
|
2265
|
+
var SCHEMA_IMPORT_KEY2 = "$schema";
|
|
2266
|
+
var enumImportKey = (name) => `$enum_${name}`;
|
|
2267
|
+
var columnTablesV2 = (definition) => definition.tables.filter((table) => !table.source);
|
|
2268
|
+
function buildDrizzleEntitiesV2(definition) {
|
|
2269
|
+
const schemaName = ormSchemaName(definition.namespace);
|
|
2270
|
+
const schema = pgCore.pgSchema(schemaName);
|
|
2271
|
+
const imports = { [SCHEMA_IMPORT_KEY2]: schema };
|
|
2272
|
+
const tables = {};
|
|
2273
|
+
const built = /* @__PURE__ */ new Map();
|
|
2274
|
+
const enums = /* @__PURE__ */ new Map();
|
|
2275
|
+
for (const enumDefinition of definition.enums) {
|
|
2276
|
+
const values = enumDefinition.values;
|
|
2277
|
+
const builtEnum = schema.enum(enumDefinition.name, values);
|
|
2278
|
+
enums.set(enumDefinition.name, builtEnum);
|
|
2279
|
+
imports[enumImportKey(enumDefinition.name)] = builtEnum;
|
|
2280
|
+
}
|
|
2281
|
+
const byName = new Map(definition.tables.map((table) => [table.table, table]));
|
|
2282
|
+
for (const table of sortByEnforcedLinks2(columnTablesV2(definition))) {
|
|
2283
|
+
const drizzleTable = buildTable2(schema, table, built, enums, byName);
|
|
2284
|
+
built.set(table.table, drizzleTable);
|
|
2285
|
+
imports[table.table] = drizzleTable;
|
|
2286
|
+
tables[table.table] = drizzleTable;
|
|
2287
|
+
}
|
|
2288
|
+
return { imports, tables, schemaName };
|
|
1489
2289
|
}
|
|
1490
|
-
function
|
|
1491
|
-
|
|
2290
|
+
function generateSnapshotV2(definition, previousId3) {
|
|
2291
|
+
const { imports, schemaName } = buildDrizzleEntitiesV2(definition);
|
|
2292
|
+
return api.generateDrizzleJson(imports, previousId3, [schemaName]);
|
|
1492
2293
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
);
|
|
1498
|
-
var MAX_CUSTOM_ROLE_DEFINITIONS = 50;
|
|
1499
|
-
var MAX_CUSTOM_ROLE_CAPABILITIES = 100;
|
|
1500
|
-
var customRoleCapabilityDeclarationSchema = zod.z.object({
|
|
1501
|
-
description: zod.z.string().max(500).optional()
|
|
1502
|
-
});
|
|
1503
|
-
var customRoleDefinitionSchema = zod.z.object({
|
|
1504
|
-
description: zod.z.string().max(500).optional(),
|
|
1505
|
-
/**
|
|
1506
|
-
* Grants this role to the host's entire runtime audience. Resolved at the
|
|
1507
|
-
* app layer at claim time — every caller who passes the host floor
|
|
1508
|
-
* (`end_user`) holds the default roles; no tuple is written for it.
|
|
1509
|
-
*/
|
|
1510
|
-
default: zod.z.boolean().default(false),
|
|
1511
|
-
/** Role keys whose grants this role also receives (privilege waterfall). */
|
|
1512
|
-
inherits: zod.z.array(customRoleKeySchema).default([]),
|
|
1513
|
-
/** Capability keys this role grants directly. */
|
|
1514
|
-
grants: zod.z.array(customRoleKeySchema).default([])
|
|
1515
|
-
});
|
|
1516
|
-
var customRolesBlockSchema = zod.z.object({
|
|
1517
|
-
/** Roles grantable to principals, keyed by role key. */
|
|
1518
|
-
definitions: zod.z.record(customRoleKeySchema, customRoleDefinitionSchema).default({}),
|
|
1519
|
-
/** Leaf capability keys the host's code checks, keyed by capability key. */
|
|
1520
|
-
capabilities: zod.z.record(customRoleKeySchema, customRoleCapabilityDeclarationSchema).default({})
|
|
1521
|
-
}).superRefine((block, ctx) => {
|
|
1522
|
-
const roleKeys = Object.keys(block.definitions);
|
|
1523
|
-
const capabilityKeys = new Set(Object.keys(block.capabilities));
|
|
1524
|
-
if (roleKeys.length > MAX_CUSTOM_ROLE_DEFINITIONS) {
|
|
1525
|
-
ctx.addIssue({
|
|
1526
|
-
code: "custom",
|
|
1527
|
-
message: `At most ${MAX_CUSTOM_ROLE_DEFINITIONS} role definitions are allowed`,
|
|
1528
|
-
path: ["definitions"]
|
|
1529
|
-
});
|
|
2294
|
+
function buildTable2(schema, table, built, enums, byName) {
|
|
2295
|
+
const primaryKeyColumns = table.primaryKey ?? [];
|
|
2296
|
+
if (primaryKeyColumns.length === 0) {
|
|
2297
|
+
throw new OrmDefinitionError(`table '${table.table}' declares no primaryKey`);
|
|
1530
2298
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
});
|
|
2299
|
+
const columnNames = new Set(table.columns.map((column) => column.columnName));
|
|
2300
|
+
for (const name of primaryKeyColumns) {
|
|
2301
|
+
if (!columnNames.has(name)) {
|
|
2302
|
+
throw new OrmDefinitionError(`primaryKey of '${table.table}' references unknown column '${name}'`);
|
|
2303
|
+
}
|
|
1537
2304
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
2305
|
+
const singlePrimaryKey = primaryKeyColumns.length === 1 ? primaryKeyColumns[0] : void 0;
|
|
2306
|
+
const compositeMembers = new Set(singlePrimaryKey === void 0 ? primaryKeyColumns : []);
|
|
2307
|
+
const columns = Object.fromEntries(
|
|
2308
|
+
table.columns.map((column) => [
|
|
2309
|
+
column.columnName,
|
|
2310
|
+
buildColumnV2({ table: table.table, column, singlePrimaryKey, compositeMembers, enums })
|
|
2311
|
+
])
|
|
2312
|
+
);
|
|
2313
|
+
const drizzleTable = schema.table(table.table, columns, (self) => [
|
|
2314
|
+
...buildCompositePrimaryKey(table, primaryKeyColumns, self),
|
|
2315
|
+
...buildIndexes2(table, self),
|
|
2316
|
+
...buildForeignKeys2(table, self, built, byName)
|
|
2317
|
+
]);
|
|
2318
|
+
return drizzleTable.enableRLS();
|
|
2319
|
+
}
|
|
2320
|
+
function resolveColumnFactory(table, column, enums) {
|
|
2321
|
+
if (typeof column.type === "string") return FACTORIES2[column.type];
|
|
2322
|
+
const enumFactory = enums.get(column.type.enum);
|
|
2323
|
+
if (!enumFactory) {
|
|
2324
|
+
throw new OrmDefinitionError(`column '${table}.${column.columnName}' references undeclared enum '${column.type.enum}'`);
|
|
2325
|
+
}
|
|
2326
|
+
return enumFactory;
|
|
2327
|
+
}
|
|
2328
|
+
function buildColumnV2({
|
|
2329
|
+
table,
|
|
2330
|
+
column,
|
|
2331
|
+
singlePrimaryKey,
|
|
2332
|
+
compositeMembers,
|
|
2333
|
+
enums
|
|
2334
|
+
}) {
|
|
2335
|
+
const factory = resolveColumnFactory(table, column, enums);
|
|
2336
|
+
let builder = factory(column.columnName);
|
|
2337
|
+
const isSinglePrimaryKey = column.columnName === singlePrimaryKey;
|
|
2338
|
+
if (isSinglePrimaryKey) builder = builder.primaryKey();
|
|
2339
|
+
if ((column.nullable === false || compositeMembers.has(column.columnName)) && !isSinglePrimaryKey) {
|
|
2340
|
+
builder = builder.notNull();
|
|
2341
|
+
}
|
|
2342
|
+
if (column.unique && !isSinglePrimaryKey) builder = builder.unique();
|
|
2343
|
+
if (column.defaultFn !== void 0 && column.default !== void 0) {
|
|
2344
|
+
throw new OrmDefinitionError(`column '${table}.${column.columnName}' declares both default and defaultFn`);
|
|
2345
|
+
}
|
|
2346
|
+
if (column.defaultFn !== void 0) {
|
|
2347
|
+
if (typeof column.type !== "string" || !isFunctionDefault(column.defaultFn, column.type)) {
|
|
2348
|
+
throw new OrmDefinitionError(
|
|
2349
|
+
`column '${table}.${column.columnName}' declares defaultFn '${column.defaultFn}' which is invalid for its type`
|
|
2350
|
+
);
|
|
1545
2351
|
}
|
|
2352
|
+
builder = builder.default(drizzleOrm.sql.raw(column.defaultFn));
|
|
2353
|
+
} else if (column.default !== void 0 && column.default !== null) {
|
|
2354
|
+
builder = builder.default(column.default);
|
|
1546
2355
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
path: ["definitions", roleKey, "inherits"]
|
|
1564
|
-
});
|
|
2356
|
+
return builder;
|
|
2357
|
+
}
|
|
2358
|
+
function buildCompositePrimaryKey(table, primaryKeyColumns, self) {
|
|
2359
|
+
if (primaryKeyColumns.length < 2) return [];
|
|
2360
|
+
return [
|
|
2361
|
+
pgCore.primaryKey({
|
|
2362
|
+
name: `${table.table}_pkey`,
|
|
2363
|
+
columns: primaryKeyColumns.map((column) => self[column])
|
|
2364
|
+
})
|
|
2365
|
+
];
|
|
2366
|
+
}
|
|
2367
|
+
function buildIndexes2(table, self) {
|
|
2368
|
+
return table.indexes.map((definition) => {
|
|
2369
|
+
for (const column of definition.columns) {
|
|
2370
|
+
if (!(column in self)) {
|
|
2371
|
+
throw new OrmDefinitionError(`index on '${table.table}' references unknown column '${column}'`);
|
|
1565
2372
|
}
|
|
1566
2373
|
}
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
code: "custom",
|
|
1580
|
-
message: `Role "${roleKey}" grants unknown capability "${granted}"`,
|
|
1581
|
-
path: ["definitions", roleKey, "grants"]
|
|
1582
|
-
});
|
|
1583
|
-
}
|
|
2374
|
+
const name = indexName(table.table, definition);
|
|
2375
|
+
const factory = definition.unique ? pgCore.uniqueIndex(name) : pgCore.index(name);
|
|
2376
|
+
const columns = definition.columns.map((column) => self[column]);
|
|
2377
|
+
return factory.on(...columns);
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
function buildForeignKeys2(table, self, built, byName) {
|
|
2381
|
+
return table.links.filter((link) => link.enforce).map((link) => {
|
|
2382
|
+
if (link.kind !== "toOne" || !link.target) {
|
|
2383
|
+
throw new OrmDefinitionError(
|
|
2384
|
+
`enforced link '${link.name}' on '${table.table}' must be a toOne link with a namespace-local target`
|
|
2385
|
+
);
|
|
1584
2386
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
if (state.get(key) === "visiting") return [...path, key];
|
|
1590
|
-
state.set(key, "visiting");
|
|
1591
|
-
const definition = block.definitions[key];
|
|
1592
|
-
for (const inherited of definition?.inherits ?? []) {
|
|
1593
|
-
if (!roleKeySet.has(inherited)) continue;
|
|
1594
|
-
const cycle = visit(inherited, [...path, key]);
|
|
1595
|
-
if (cycle) return cycle;
|
|
2387
|
+
const targetTable = byName.get(link.target);
|
|
2388
|
+
const target = link.target === table.table ? self : built.get(link.target);
|
|
2389
|
+
if (!targetTable || !target) {
|
|
2390
|
+
throw new OrmDefinitionError(`enforced link '${link.name}' on '${table.table}' has no built target '${link.target}'`);
|
|
1596
2391
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
2392
|
+
const referencedColumn = link.references ?? singlePrimaryKeyOf(targetTable);
|
|
2393
|
+
if (!referencedColumn) {
|
|
2394
|
+
throw new OrmDefinitionError(
|
|
2395
|
+
`enforced link '${link.name}' on '${table.table}' targets '${link.target}' whose primary key is composite \u2014 declare 'references'`
|
|
2396
|
+
);
|
|
2397
|
+
}
|
|
2398
|
+
const targetColumn = target[referencedColumn];
|
|
2399
|
+
const sourceColumn = self[link.on];
|
|
2400
|
+
if (!targetColumn || !sourceColumn) {
|
|
2401
|
+
throw new OrmDefinitionError(
|
|
2402
|
+
`enforced link '${link.name}' on '${table.table}' references unknown column '${targetColumn ? link.on : referencedColumn}'`
|
|
2403
|
+
);
|
|
2404
|
+
}
|
|
2405
|
+
return pgCore.foreignKey({
|
|
2406
|
+
name: foreignKeyName(table.table, link.name),
|
|
2407
|
+
columns: [sourceColumn],
|
|
2408
|
+
foreignColumns: [targetColumn]
|
|
2409
|
+
});
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
var singlePrimaryKeyOf = (table) => table.primaryKey?.length === 1 ? table.primaryKey[0] : void 0;
|
|
2413
|
+
function sortByEnforcedLinks2(tables) {
|
|
2414
|
+
const remaining = new Map(tables.map((table) => [table.table, table]));
|
|
2415
|
+
const sorted = [];
|
|
2416
|
+
const placed = /* @__PURE__ */ new Set();
|
|
2417
|
+
while (remaining.size > 0) {
|
|
2418
|
+
const ready = [...remaining.values()].filter(
|
|
2419
|
+
(table) => enforcedTargets2(table).every((target) => placed.has(target) || !remaining.has(target))
|
|
2420
|
+
);
|
|
2421
|
+
if (ready.length === 0) {
|
|
2422
|
+
throw new OrmDefinitionError(`enforced links form a cycle among: ${[...remaining.keys()].join(", ")}`);
|
|
2423
|
+
}
|
|
2424
|
+
for (const table of ready) {
|
|
2425
|
+
sorted.push(table);
|
|
2426
|
+
placed.add(table.table);
|
|
2427
|
+
remaining.delete(table.table);
|
|
1609
2428
|
}
|
|
1610
2429
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
var MAX_CAPABILITY_PINS = 100;
|
|
1618
|
-
var capabilityPinsSchema = zod.z.array(
|
|
1619
|
-
zod.z.string().min(1).max(68).regex(
|
|
1620
|
-
CUSTOM_CAPABILITY_PIN_RE,
|
|
1621
|
-
'Pins must be "org/<key>" or a bare "<key>" matching [a-z0-9-]+'
|
|
2430
|
+
return sorted;
|
|
2431
|
+
}
|
|
2432
|
+
var enforcedTargets2 = (table) => table.links.flatMap(
|
|
2433
|
+
(link) => (
|
|
2434
|
+
// A self-target is not an ordering dependency — the FK builds inline.
|
|
2435
|
+
link.enforce && link.target !== void 0 && link.target !== table.table ? [link.target] : []
|
|
1622
2436
|
)
|
|
1623
|
-
)
|
|
1624
|
-
function
|
|
1625
|
-
|
|
2437
|
+
);
|
|
2438
|
+
function applyMigrationHintsV2(previous, definition) {
|
|
2439
|
+
const adjusted = structuredClone(previous);
|
|
2440
|
+
const statements = [];
|
|
2441
|
+
const schemaName = ormSchemaName(definition.namespace);
|
|
2442
|
+
const qualified = (table) => `${quoteIdentifier(schemaName)}.${quoteIdentifier(table)}`;
|
|
2443
|
+
const enumsByName = new Map(definition.enums.map((entry) => [entry.name, entry]));
|
|
2444
|
+
for (const table of columnTablesV2(definition)) {
|
|
2445
|
+
if (table.renamedFrom) {
|
|
2446
|
+
applyTableRename({ adjusted, schemaName, table, renamedFrom: table.renamedFrom, statements, qualified });
|
|
2447
|
+
}
|
|
2448
|
+
const snapshotTable = findTable2(adjusted, schemaName, table.table);
|
|
2449
|
+
if (!snapshotTable) continue;
|
|
2450
|
+
for (const column of table.columns) {
|
|
2451
|
+
const name = column.columnName;
|
|
2452
|
+
if (column.renamedFrom) {
|
|
2453
|
+
const previousColumn = snapshotTable.columns[column.renamedFrom];
|
|
2454
|
+
if (previousColumn && snapshotTable.columns[name]) {
|
|
2455
|
+
throw new OrmDefinitionError(
|
|
2456
|
+
`'${table.table}.${name}' declares renamedFrom '${column.renamedFrom}', but both columns exist in the previous schema \u2014 a rename cannot overwrite a live column`
|
|
2457
|
+
);
|
|
2458
|
+
}
|
|
2459
|
+
if (previousColumn) {
|
|
2460
|
+
statements.push(
|
|
2461
|
+
`ALTER TABLE ${qualified(table.table)} RENAME COLUMN ${quoteIdentifier(column.renamedFrom)} TO ${quoteIdentifier(name)}`
|
|
2462
|
+
);
|
|
2463
|
+
delete snapshotTable.columns[column.renamedFrom];
|
|
2464
|
+
snapshotTable.columns[name] = { ...previousColumn, name };
|
|
2465
|
+
} else if (!snapshotTable.columns[name]) {
|
|
2466
|
+
throw new OrmDefinitionError(
|
|
2467
|
+
`'${table.table}.${name}' declares renamedFrom '${column.renamedFrom}', but the previous schema has neither column \u2014 check the declared source name`
|
|
2468
|
+
);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
const cast = column.cast ? castParts(column.cast) : null;
|
|
2472
|
+
if (cast) {
|
|
2473
|
+
const current = snapshotTable.columns[name];
|
|
2474
|
+
const { snapshotType, sqlType } = castTargetTypes(column, enumsByName, schemaName, table.table);
|
|
2475
|
+
if (current && current.type !== snapshotType) {
|
|
2476
|
+
statements.push(
|
|
2477
|
+
`ALTER TABLE ${qualified(table.table)} ALTER COLUMN ${quoteIdentifier(name)} TYPE ${sqlType} USING ${quoteIdentifier(name)}::${sqlType}`
|
|
2478
|
+
);
|
|
2479
|
+
current.type = snapshotType;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
return { statements, adjusted };
|
|
2485
|
+
}
|
|
2486
|
+
function castTargetTypes(column, enums, schemaName, table) {
|
|
2487
|
+
if (typeof column.type === "string") {
|
|
2488
|
+
const name = sqlTypeName(column.type);
|
|
2489
|
+
return { snapshotType: name, sqlType: name };
|
|
2490
|
+
}
|
|
2491
|
+
const enumName = column.type.enum;
|
|
2492
|
+
if (!enums.has(enumName)) {
|
|
2493
|
+
throw new OrmDefinitionError(`column '${table}.${column.columnName}' casts to undeclared enum '${enumName}'`);
|
|
2494
|
+
}
|
|
2495
|
+
return { snapshotType: enumName, sqlType: `${quoteIdentifier(schemaName)}.${quoteIdentifier(enumName)}` };
|
|
1626
2496
|
}
|
|
1627
|
-
function
|
|
1628
|
-
|
|
1629
|
-
|
|
2497
|
+
function applyTableRename({
|
|
2498
|
+
adjusted,
|
|
2499
|
+
schemaName,
|
|
2500
|
+
table,
|
|
2501
|
+
renamedFrom,
|
|
2502
|
+
statements,
|
|
2503
|
+
qualified
|
|
1630
2504
|
}) {
|
|
1631
|
-
const
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
2505
|
+
const tables = adjusted.tables ?? {};
|
|
2506
|
+
const previousEntry = Object.entries(tables).find(
|
|
2507
|
+
([, entry]) => entry.schema === schemaName && entry.name === renamedFrom
|
|
2508
|
+
);
|
|
2509
|
+
const currentTable = findTable2(adjusted, schemaName, table.table);
|
|
2510
|
+
if (previousEntry && currentTable) {
|
|
2511
|
+
throw new OrmDefinitionError(
|
|
2512
|
+
`'${table.table}' declares renamedFrom '${renamedFrom}', but both tables exist in the previous schema \u2014 a rename cannot overwrite a live table`
|
|
2513
|
+
);
|
|
2514
|
+
}
|
|
2515
|
+
if (!previousEntry) {
|
|
2516
|
+
if (currentTable) return;
|
|
2517
|
+
throw new OrmDefinitionError(
|
|
2518
|
+
`'${table.table}' declares renamedFrom '${renamedFrom}', but the previous schema has neither table \u2014 check the declared source name`
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2521
|
+
statements.push(`ALTER TABLE ${qualified(renamedFrom)} RENAME TO ${quoteIdentifier(table.table)}`);
|
|
2522
|
+
const [previousKey, previousTable] = previousEntry;
|
|
2523
|
+
delete tables[previousKey];
|
|
2524
|
+
tables[`${schemaName}.${table.table}`] = { ...previousTable, name: table.table };
|
|
2525
|
+
}
|
|
2526
|
+
function findTable2(snapshot, schema, table) {
|
|
2527
|
+
const tables = snapshot.tables ?? {};
|
|
2528
|
+
return Object.values(tables).find((entry) => entry.schema === schema && entry.name === table);
|
|
2529
|
+
}
|
|
2530
|
+
async function planMigrationV2({
|
|
2531
|
+
canonical,
|
|
2532
|
+
previousSnapshot
|
|
2533
|
+
}) {
|
|
2534
|
+
const schema = ormSchemaName(canonical.namespace);
|
|
2535
|
+
const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(canonical.namespace);
|
|
2536
|
+
const hints = applyMigrationHintsV2(previous, canonical);
|
|
2537
|
+
const snapshot = generateSnapshotV2(canonical, previousId2(previous));
|
|
2538
|
+
const ddl = await diffStatements(hints.adjusted, snapshot);
|
|
2539
|
+
const statements = [
|
|
2540
|
+
...hints.statements.map((statement) => ({ kind: "hint", sql: statement })),
|
|
2541
|
+
...ddl.map((statement) => ({ kind: "ddl", sql: statement.replace(/;\s*$/, "") }))
|
|
2542
|
+
];
|
|
2543
|
+
return { namespace: canonical.namespace, schema, statements, snapshot };
|
|
2544
|
+
}
|
|
2545
|
+
var previousId2 = (snapshot) => snapshot.id;
|
|
2546
|
+
var IR_VERSION = 1;
|
|
2547
|
+
var CLAIMS_CONTRACT = {
|
|
2548
|
+
sub: "string",
|
|
2549
|
+
org: "string?",
|
|
2550
|
+
roles: "string[]",
|
|
2551
|
+
capabilities: "string[]"
|
|
2552
|
+
};
|
|
2553
|
+
var scalarLiteral = zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]);
|
|
2554
|
+
var valueIrSchema = zod.z.discriminatedUnion("kind", [
|
|
2555
|
+
zod.z.object({ kind: zod.z.literal("literal"), value: zod.z.union([scalarLiteral, zod.z.array(scalarLiteral)]) }),
|
|
2556
|
+
zod.z.object({ kind: zod.z.literal("variable"), name: zod.z.string() }),
|
|
2557
|
+
zod.z.object({ kind: zod.z.literal("claim"), key: zod.z.enum(["sub", "org"]) })
|
|
2558
|
+
]);
|
|
2559
|
+
var CMP_OPS = [
|
|
2560
|
+
"eq",
|
|
2561
|
+
"neq",
|
|
2562
|
+
"gt",
|
|
2563
|
+
"gte",
|
|
2564
|
+
"lt",
|
|
2565
|
+
"lte",
|
|
2566
|
+
"in",
|
|
2567
|
+
"nin",
|
|
2568
|
+
"like",
|
|
2569
|
+
"ilike",
|
|
2570
|
+
"isNull",
|
|
2571
|
+
"contains",
|
|
2572
|
+
"hasKey"
|
|
2573
|
+
];
|
|
2574
|
+
var predIrSchema = zod.z.lazy(
|
|
2575
|
+
() => zod.z.discriminatedUnion("kind", [
|
|
2576
|
+
zod.z.object({ kind: zod.z.literal("and"), operands: zod.z.array(predIrSchema).min(1) }),
|
|
2577
|
+
zod.z.object({ kind: zod.z.literal("or"), operands: zod.z.array(predIrSchema).min(1) }),
|
|
2578
|
+
zod.z.object({ kind: zod.z.literal("not"), operand: predIrSchema }),
|
|
2579
|
+
zod.z.object({ kind: zod.z.literal("cmp"), op: zod.z.enum(CMP_OPS), column: zod.z.string(), value: valueIrSchema }),
|
|
2580
|
+
zod.z.object({ kind: zod.z.literal("exists"), link: zod.z.string(), where: predIrSchema }),
|
|
2581
|
+
zod.z.object({
|
|
2582
|
+
kind: zod.z.literal("aggPredicate"),
|
|
2583
|
+
link: zod.z.string(),
|
|
2584
|
+
fn: zod.z.literal("count"),
|
|
2585
|
+
op: zod.z.enum(["eq", "neq", "gt", "gte", "lt", "lte"]),
|
|
2586
|
+
value: zod.z.number(),
|
|
2587
|
+
where: predIrSchema.optional()
|
|
2588
|
+
}),
|
|
2589
|
+
zod.z.object({ kind: zod.z.literal("capability"), key: zod.z.string() }),
|
|
2590
|
+
zod.z.object({ kind: zod.z.literal("role"), key: zod.z.string() }),
|
|
2591
|
+
zod.z.object({ kind: zod.z.literal("dynamicFilter"), slot: zod.z.string() }),
|
|
2592
|
+
zod.z.object({ kind: zod.z.literal("const"), value: zod.z.boolean() })
|
|
2593
|
+
])
|
|
2594
|
+
);
|
|
2595
|
+
var orderIrSchema = zod.z.discriminatedUnion("kind", [
|
|
2596
|
+
zod.z.object({
|
|
2597
|
+
kind: zod.z.literal("column"),
|
|
2598
|
+
column: zod.z.string(),
|
|
2599
|
+
/** Order through a to-one link: `link` is set and `column` is on the target. */
|
|
2600
|
+
link: zod.z.string().optional(),
|
|
2601
|
+
direction: zod.z.enum(["asc", "desc"]),
|
|
2602
|
+
nulls: zod.z.enum(["first", "last"]).optional()
|
|
2603
|
+
}),
|
|
2604
|
+
/** §4f: `$orderBy: [FooOrderBy!]` variable — validated + lowered at runtime. */
|
|
2605
|
+
zod.z.object({ kind: zod.z.literal("dynamic"), slot: zod.z.string() })
|
|
2606
|
+
]);
|
|
2607
|
+
var limitIrSchema = zod.z.object({
|
|
2608
|
+
/** Fixed value or a variable, clamped to [1, max]; absent → default. */
|
|
2609
|
+
variable: zod.z.string().optional(),
|
|
2610
|
+
value: zod.z.number().int().positive().optional(),
|
|
2611
|
+
default: zod.z.number().int().positive(),
|
|
2612
|
+
max: zod.z.number().int().positive()
|
|
2613
|
+
});
|
|
2614
|
+
var selectionIrSchema = zod.z.lazy(
|
|
2615
|
+
() => zod.z.object({
|
|
2616
|
+
typename: zod.z.string(),
|
|
2617
|
+
columns: zod.z.array(zod.z.object({ alias: zod.z.string(), column: zod.z.string() })),
|
|
2618
|
+
relations: zod.z.array(zod.z.object({
|
|
2619
|
+
alias: zod.z.string(),
|
|
2620
|
+
link: zod.z.string(),
|
|
2621
|
+
selection: selectionIrSchema,
|
|
2622
|
+
where: predIrSchema.optional(),
|
|
2623
|
+
orderBy: zod.z.array(orderIrSchema).optional(),
|
|
2624
|
+
limit: limitIrSchema.optional(),
|
|
2625
|
+
offset: zod.z.object({ variable: zod.z.string().optional(), value: zod.z.number().int().optional() }).optional()
|
|
2626
|
+
})),
|
|
2627
|
+
aggregates: zod.z.array(zod.z.object({
|
|
2628
|
+
alias: zod.z.string(),
|
|
2629
|
+
link: zod.z.string().optional(),
|
|
2630
|
+
where: predIrSchema.optional(),
|
|
2631
|
+
count: zod.z.object({ columns: zod.z.array(zod.z.string()).optional(), distinct: zod.z.boolean().optional() }).optional(),
|
|
2632
|
+
sum: zod.z.array(zod.z.string()).optional(),
|
|
2633
|
+
avg: zod.z.array(zod.z.string()).optional(),
|
|
2634
|
+
nodes: selectionIrSchema.optional()
|
|
2635
|
+
}))
|
|
2636
|
+
})
|
|
2637
|
+
);
|
|
2638
|
+
var includeIfSchema = zod.z.object({ variable: zod.z.string(), negated: zod.z.boolean() });
|
|
2639
|
+
var selectRootSchema = zod.z.object({
|
|
2640
|
+
kind: zod.z.literal("select"),
|
|
2641
|
+
alias: zod.z.string(),
|
|
2642
|
+
table: zod.z.string(),
|
|
2643
|
+
selection: selectionIrSchema,
|
|
2644
|
+
/** Composed at compile time: AND(policy, documentWhere?, dynamicFilter?). */
|
|
2645
|
+
where: predIrSchema,
|
|
2646
|
+
orderBy: zod.z.array(orderIrSchema).default([]),
|
|
2647
|
+
distinctOn: zod.z.union([zod.z.array(zod.z.string()), zod.z.object({ slot: zod.z.string() })]).optional(),
|
|
2648
|
+
limit: limitIrSchema,
|
|
2649
|
+
offset: zod.z.object({ variable: zod.z.string().optional(), value: zod.z.number().int().optional() }).optional(),
|
|
2650
|
+
includeIf: includeIfSchema.optional()
|
|
2651
|
+
});
|
|
2652
|
+
var byPkRootSchema = zod.z.object({
|
|
2653
|
+
kind: zod.z.literal("byPk"),
|
|
2654
|
+
alias: zod.z.string(),
|
|
2655
|
+
table: zod.z.string(),
|
|
2656
|
+
selection: selectionIrSchema,
|
|
2657
|
+
pk: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })).min(1),
|
|
2658
|
+
/** Policy predicate — ANDed with the PK match. */
|
|
2659
|
+
where: predIrSchema,
|
|
2660
|
+
includeIf: includeIfSchema.optional()
|
|
2661
|
+
});
|
|
2662
|
+
var aggregateRootSchema = zod.z.object({
|
|
2663
|
+
kind: zod.z.literal("aggregate"),
|
|
2664
|
+
alias: zod.z.string(),
|
|
2665
|
+
table: zod.z.string(),
|
|
2666
|
+
where: predIrSchema,
|
|
2667
|
+
count: zod.z.object({ columns: zod.z.array(zod.z.string()).optional(), distinct: zod.z.boolean().optional() }).optional(),
|
|
2668
|
+
sum: zod.z.array(zod.z.string()).optional(),
|
|
2669
|
+
avg: zod.z.array(zod.z.string()).optional(),
|
|
2670
|
+
nodes: selectionIrSchema.optional(),
|
|
2671
|
+
includeIf: includeIfSchema.optional()
|
|
2672
|
+
});
|
|
2673
|
+
var writeValuesSchema = zod.z.union([
|
|
2674
|
+
zod.z.object({ slot: zod.z.string() }),
|
|
2675
|
+
zod.z.object({ entries: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })) })
|
|
2676
|
+
]);
|
|
2677
|
+
var onConflictIrSchema = zod.z.union([
|
|
2678
|
+
zod.z.object({ slot: zod.z.string() }),
|
|
2679
|
+
zod.z.object({ constraint: zod.z.string(), updateColumns: zod.z.array(zod.z.string()) })
|
|
2680
|
+
]);
|
|
2681
|
+
var mutationRootSchema = zod.z.object({
|
|
2682
|
+
kind: zod.z.literal("mutation"),
|
|
2683
|
+
op: zod.z.enum(["insert", "insertOne", "update", "updateByPk", "delete", "deleteByPk"]),
|
|
2684
|
+
alias: zod.z.string(),
|
|
2685
|
+
table: zod.z.string(),
|
|
2686
|
+
/** insert/insertOne: rows come from a slot ($object/$objects) or literals. */
|
|
2687
|
+
objects: zod.z.union([
|
|
2688
|
+
zod.z.object({ slot: zod.z.string() }),
|
|
2689
|
+
zod.z.object({ rows: zod.z.array(zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema }))) })
|
|
2690
|
+
]).optional(),
|
|
2691
|
+
/** Server-pinned columns (e.g. author_sub ← claim('sub')) merged into every inserted row. */
|
|
2692
|
+
pinnedColumns: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })).default([]),
|
|
2693
|
+
onConflict: onConflictIrSchema.optional(),
|
|
2694
|
+
set: writeValuesSchema.optional(),
|
|
2695
|
+
append: writeValuesSchema.optional(),
|
|
2696
|
+
inc: writeValuesSchema.optional(),
|
|
2697
|
+
/** update/delete row filter: AND(writePolicy, documentWhere/pk, dynamicFilter?). */
|
|
2698
|
+
where: predIrSchema.optional(),
|
|
2699
|
+
pk: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })).optional(),
|
|
2700
|
+
/** WITH CHECK analogue evaluated against NEW row values (insert/update). */
|
|
2701
|
+
insertCheck: predIrSchema.optional(),
|
|
2702
|
+
returning: selectionIrSchema.nullable(),
|
|
2703
|
+
affectedRows: zod.z.boolean().default(false),
|
|
2704
|
+
includeIf: includeIfSchema.optional()
|
|
2705
|
+
});
|
|
2706
|
+
var actionRootSchema = zod.z.object({
|
|
2707
|
+
kind: zod.z.literal("action"),
|
|
2708
|
+
alias: zod.z.string(),
|
|
2709
|
+
name: zod.z.string(),
|
|
2710
|
+
params: zod.z.array(zod.z.object({ name: zod.z.string(), value: valueIrSchema })),
|
|
2711
|
+
returns: zod.z.enum(["rows", "rowsAffected", "scalar"])
|
|
2712
|
+
});
|
|
2713
|
+
var rootIrSchema = zod.z.discriminatedUnion("kind", [
|
|
2714
|
+
selectRootSchema,
|
|
2715
|
+
byPkRootSchema,
|
|
2716
|
+
aggregateRootSchema,
|
|
2717
|
+
mutationRootSchema,
|
|
2718
|
+
actionRootSchema
|
|
2719
|
+
]);
|
|
2720
|
+
var INPUT_REF_KINDS = [
|
|
2721
|
+
"boolExp",
|
|
2722
|
+
"orderBy",
|
|
2723
|
+
"selectColumn",
|
|
2724
|
+
"insertInput",
|
|
2725
|
+
"setInput",
|
|
2726
|
+
"appendInput",
|
|
2727
|
+
"onConflict"
|
|
2728
|
+
];
|
|
2729
|
+
var variableDefaultIrSchema = zod.z.object({
|
|
2730
|
+
value: zod.z.union([scalarLiteral, zod.z.array(scalarLiteral)])
|
|
2731
|
+
});
|
|
2732
|
+
var variableIrSchema = zod.z.object({
|
|
2733
|
+
name: zod.z.string(),
|
|
2734
|
+
type: zod.z.union([
|
|
2735
|
+
zod.z.object({ kind: zod.z.literal("scalar"), scalar: zod.z.enum(COLUMN_TYPES) }),
|
|
2736
|
+
/** `values` is the declared membership whitelist — the executor validates against it. */
|
|
2737
|
+
zod.z.object({ kind: zod.z.literal("enum"), name: zod.z.string(), values: zod.z.array(zod.z.string()).min(1) }),
|
|
2738
|
+
/** §4f dynamic slot: validated against `tables[table]` whitelists at runtime. */
|
|
2739
|
+
zod.z.object({ kind: zod.z.literal("inputRef"), input: zod.z.enum(INPUT_REF_KINDS), table: zod.z.string() })
|
|
2740
|
+
]),
|
|
2741
|
+
nullable: zod.z.boolean(),
|
|
2742
|
+
list: zod.z.boolean(),
|
|
2743
|
+
/** Absent = no default declared; see {@link variableDefaultIrSchema}. */
|
|
2744
|
+
defaultValue: variableDefaultIrSchema.optional()
|
|
2745
|
+
});
|
|
2746
|
+
var compiledOperationSchema = zod.z.object({
|
|
2747
|
+
irVersion: zod.z.literal(IR_VERSION),
|
|
2748
|
+
name: zod.z.string(),
|
|
2749
|
+
opHash: zod.z.string().regex(/^sha256:[0-9a-f]{64}$/),
|
|
2750
|
+
kind: zod.z.enum(["query", "mutation", "action"]),
|
|
2751
|
+
requires: zod.z.object({
|
|
2752
|
+
capability: zod.z.string().optional(),
|
|
2753
|
+
role: zod.z.string().optional()
|
|
2754
|
+
}).nullable(),
|
|
2755
|
+
variables: zod.z.array(variableIrSchema),
|
|
2756
|
+
/** Every table the op touches — data-token scoping + audit. */
|
|
2757
|
+
tableSet: zod.z.array(zod.z.string()).min(1),
|
|
2758
|
+
roots: zod.z.array(rootIrSchema).min(1)
|
|
2759
|
+
});
|
|
2760
|
+
var tableMetaIrSchema = zod.z.object({
|
|
2761
|
+
table: zod.z.string(),
|
|
2762
|
+
schema: zod.z.string(),
|
|
2763
|
+
typename: zod.z.string(),
|
|
2764
|
+
columns: zod.z.array(zod.z.object({
|
|
2765
|
+
name: zod.z.string(),
|
|
2766
|
+
columnName: zod.z.string(),
|
|
2767
|
+
type: zod.z.union([zod.z.enum(COLUMN_TYPES), zod.z.object({ enum: zod.z.string(), values: zod.z.array(zod.z.string()) })]),
|
|
2768
|
+
nullable: zod.z.boolean()
|
|
2769
|
+
})),
|
|
2770
|
+
primaryKey: zod.z.array(zod.z.string()).min(1),
|
|
2771
|
+
links: zod.z.array(zod.z.object({
|
|
2772
|
+
name: zod.z.string(),
|
|
2773
|
+
kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
|
|
2774
|
+
target: zod.z.string(),
|
|
2775
|
+
on: zod.z.string(),
|
|
2776
|
+
references: zod.z.string()
|
|
2777
|
+
})),
|
|
2778
|
+
/** §4f whitelists: what dynamic inputs may reference. */
|
|
2779
|
+
filterable: zod.z.array(zod.z.string()),
|
|
2780
|
+
orderable: zod.z.array(zod.z.string()),
|
|
2781
|
+
insertable: zod.z.array(zod.z.string()),
|
|
2782
|
+
settable: zod.z.array(zod.z.string()),
|
|
2783
|
+
appendable: zod.z.array(zod.z.string()),
|
|
2784
|
+
constraints: zod.z.array(zod.z.object({ name: zod.z.string(), columns: zod.z.array(zod.z.string()) })),
|
|
2785
|
+
/** Depth/node caps for dynamic filter trees on this table. */
|
|
2786
|
+
dynamicFilterMaxDepth: zod.z.number().int().positive().default(6),
|
|
2787
|
+
dynamicFilterMaxNodes: zod.z.number().int().positive().default(64)
|
|
2788
|
+
});
|
|
2789
|
+
var compiledArtifactSchema = zod.z.object({
|
|
2790
|
+
irVersion: zod.z.literal(IR_VERSION),
|
|
2791
|
+
namespace: zod.z.string(),
|
|
2792
|
+
schemaHash: zod.z.string(),
|
|
2793
|
+
opsHash: zod.z.string(),
|
|
2794
|
+
tables: zod.z.record(zod.z.string(), tableMetaIrSchema),
|
|
2795
|
+
operations: zod.z.record(zod.z.string(), compiledOperationSchema)
|
|
2796
|
+
});
|
|
2797
|
+
var IrVersionError = class extends Error {
|
|
2798
|
+
constructor(found) {
|
|
2799
|
+
super(`Unsupported IR version ${String(found)} \u2014 this executor supports up to ${IR_VERSION}. Upgrade @sequenceholdings/orm or re-push the namespace.`);
|
|
2800
|
+
this.name = "IrVersionError";
|
|
1641
2801
|
}
|
|
1642
|
-
|
|
2802
|
+
};
|
|
2803
|
+
function normalizeIr(raw) {
|
|
2804
|
+
if (!raw || typeof raw !== "object") throw new IrVersionError(void 0);
|
|
2805
|
+
const version = raw.irVersion;
|
|
2806
|
+
if (version !== IR_VERSION) throw new IrVersionError(version);
|
|
2807
|
+
return compiledArtifactSchema.parse(raw);
|
|
2808
|
+
}
|
|
2809
|
+
function indexByHash(artifact) {
|
|
2810
|
+
const map = /* @__PURE__ */ new Map();
|
|
2811
|
+
for (const op of Object.values(artifact.operations)) map.set(op.opHash, op);
|
|
2812
|
+
return map;
|
|
1643
2813
|
}
|
|
1644
2814
|
|
|
1645
2815
|
// src/schema/namespace.ts
|
|
@@ -1673,7 +2843,7 @@ function buildNamespaceDefinition(files, config) {
|
|
|
1673
2843
|
}
|
|
1674
2844
|
}
|
|
1675
2845
|
const capabilities = config?.capabilities ? { roles: config.capabilities.roles ?? EMPTY_CAPABILITIES.roles, uses: config.capabilities.uses ?? [] } : EMPTY_CAPABILITIES;
|
|
1676
|
-
const pinIssues = validateCapabilityPins({ uses: capabilities.uses, roles: capabilities.roles });
|
|
2846
|
+
const pinIssues = customRolesManifest.validateCapabilityPins({ uses: capabilities.uses, roles: capabilities.roles });
|
|
1677
2847
|
if (pinIssues.length > 0) throw new OrmDefinitionError(pinIssues.join("; "), "namespace.yaml");
|
|
1678
2848
|
const tables = files.map(({ table }) => table).sort((a, b) => a.table.localeCompare(b.table));
|
|
1679
2849
|
const byName = new Map(tables.map((table) => [table.table, table]));
|
|
@@ -1783,8 +2953,8 @@ var namespaceConfigSchema = zod.z.strictObject({
|
|
|
1783
2953
|
namespace: namespaceSchema,
|
|
1784
2954
|
description: zod.z.string().optional(),
|
|
1785
2955
|
capabilities: zod.z.strictObject({
|
|
1786
|
-
roles: customRolesBlockSchema.optional(),
|
|
1787
|
-
uses: capabilityPinsSchema.optional()
|
|
2956
|
+
roles: customRolesManifest.customRolesBlockSchema.optional(),
|
|
2957
|
+
uses: customRolesManifest.capabilityPinsSchema.optional()
|
|
1788
2958
|
}).optional()
|
|
1789
2959
|
});
|
|
1790
2960
|
|
|
@@ -1897,8 +3067,8 @@ async function readMigrationWorkspace(dir, namespace) {
|
|
|
1897
3067
|
journalPath
|
|
1898
3068
|
);
|
|
1899
3069
|
}
|
|
1900
|
-
parsed.data.entries.forEach((entry,
|
|
1901
|
-
if (entry.ordinal !==
|
|
3070
|
+
parsed.data.entries.forEach((entry, index3) => {
|
|
3071
|
+
if (entry.ordinal !== index3 + 1) {
|
|
1902
3072
|
throw new OrmDefinitionError(`migration journal entries must be contiguous from 1 (found ${entry.name})`, journalPath);
|
|
1903
3073
|
}
|
|
1904
3074
|
});
|
|
@@ -1921,7 +3091,7 @@ async function readMigrationSql(dir, entry) {
|
|
|
1921
3091
|
throw new OrmDefinitionError("missing migration file", path$1);
|
|
1922
3092
|
});
|
|
1923
3093
|
}
|
|
1924
|
-
var checksumOf = (
|
|
3094
|
+
var checksumOf = (text3) => crypto.createHash("sha256").update(text3).digest("hex");
|
|
1925
3095
|
var migrationName = (ordinal, contentHash) => `${pad(ordinal)}_${contentHash.slice(0, 12)}`;
|
|
1926
3096
|
function renderMigrationSql({
|
|
1927
3097
|
name,
|
|
@@ -1947,12 +3117,12 @@ async function writeMigration({
|
|
|
1947
3117
|
const tip = workspace.journal.entries.at(-1);
|
|
1948
3118
|
const ordinal = (tip?.ordinal ?? 0) + 1;
|
|
1949
3119
|
const name = migrationName(ordinal, contentHash);
|
|
1950
|
-
const
|
|
3120
|
+
const sql3 = renderMigrationSql({ name, namespace: workspace.journal.namespace, contentHash, plan });
|
|
1951
3121
|
const metaDir = path.join(dir, MIGRATIONS_DIRNAME, META_DIRNAME);
|
|
1952
3122
|
await promises.mkdir(metaDir, { recursive: true });
|
|
1953
3123
|
const snapshotText = `${JSON.stringify(plan.snapshot, null, 2)}
|
|
1954
3124
|
`;
|
|
1955
|
-
await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${name}.sql`),
|
|
3125
|
+
await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${name}.sql`), sql3);
|
|
1956
3126
|
await promises.writeFile(path.join(metaDir, `${pad(ordinal)}.snapshot.json`), snapshotText);
|
|
1957
3127
|
const journal = {
|
|
1958
3128
|
...workspace.journal,
|
|
@@ -1963,7 +3133,7 @@ async function writeMigration({
|
|
|
1963
3133
|
name,
|
|
1964
3134
|
content_hash: contentHash,
|
|
1965
3135
|
previous_content_hash: tip?.content_hash ?? null,
|
|
1966
|
-
checksum: checksumOf(
|
|
3136
|
+
checksum: checksumOf(sql3),
|
|
1967
3137
|
snapshot_checksum: checksumOf(snapshotText),
|
|
1968
3138
|
destructive
|
|
1969
3139
|
}
|
|
@@ -2075,11 +3245,11 @@ async function checkWorkspace({
|
|
|
2075
3245
|
log(`no committed migrations for '${loaded.definition.namespace}' \u2014 run \`seq-studio orm diff\` and commit the result`);
|
|
2076
3246
|
return 1;
|
|
2077
3247
|
}
|
|
2078
|
-
let
|
|
3248
|
+
let previousId3;
|
|
2079
3249
|
let previousHash = null;
|
|
2080
3250
|
for (const entry of workspace.journal.entries) {
|
|
2081
|
-
const
|
|
2082
|
-
if (checksumOf(
|
|
3251
|
+
const sql3 = await readMigrationSql(dir, entry);
|
|
3252
|
+
if (checksumOf(sql3) !== entry.checksum) {
|
|
2083
3253
|
log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
|
|
2084
3254
|
return 1;
|
|
2085
3255
|
}
|
|
@@ -2089,7 +3259,7 @@ async function checkWorkspace({
|
|
|
2089
3259
|
return 1;
|
|
2090
3260
|
}
|
|
2091
3261
|
const snapshot = JSON.parse(snapshotText);
|
|
2092
|
-
if (entry.ordinal > 1 && snapshot.prevId !==
|
|
3262
|
+
if (entry.ordinal > 1 && snapshot.prevId !== previousId3) {
|
|
2093
3263
|
log(`snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``);
|
|
2094
3264
|
return 1;
|
|
2095
3265
|
}
|
|
@@ -2097,7 +3267,7 @@ async function checkWorkspace({
|
|
|
2097
3267
|
log(`content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``);
|
|
2098
3268
|
return 1;
|
|
2099
3269
|
}
|
|
2100
|
-
|
|
3270
|
+
previousId3 = snapshot.id;
|
|
2101
3271
|
previousHash = entry.content_hash;
|
|
2102
3272
|
}
|
|
2103
3273
|
if (tip.content_hash !== loaded.contentHash) {
|
|
@@ -2119,12 +3289,161 @@ async function checkWorkspace({
|
|
|
2119
3289
|
log(`migrations in sync \u2014 ${workspace.journal.entries.length} committed, tip ${tip.name}`);
|
|
2120
3290
|
return 0;
|
|
2121
3291
|
}
|
|
2122
|
-
var firstLine = (
|
|
3292
|
+
var firstLine = (sql3) => sql3.split("\n")[0] ?? sql3;
|
|
3293
|
+
|
|
3294
|
+
// src/migrations/from-yaml.ts
|
|
3295
|
+
function convertV1DefinitionToCanonical(definition) {
|
|
3296
|
+
const raw = {
|
|
3297
|
+
definition_format: CANONICAL_FORMAT,
|
|
3298
|
+
namespace: definition.namespace,
|
|
3299
|
+
description: definition.description ?? void 0,
|
|
3300
|
+
enums: [],
|
|
3301
|
+
tables: definition.tables.map(convertTable),
|
|
3302
|
+
capabilities: {
|
|
3303
|
+
roles: definition.capabilities.roles,
|
|
3304
|
+
uses: [...definition.capabilities.uses]
|
|
3305
|
+
}
|
|
3306
|
+
};
|
|
3307
|
+
const parsed = canonicalDefinitionSchema.safeParse(raw);
|
|
3308
|
+
if (!parsed.success) {
|
|
3309
|
+
const issue = parsed.error.issues[0];
|
|
3310
|
+
throw new OrmDefinitionError(
|
|
3311
|
+
`converted definition is not valid canonical v2: ${issue ? `${issue.path.join(".")}: ${issue.message}` : "unknown issue"}`
|
|
3312
|
+
);
|
|
3313
|
+
}
|
|
3314
|
+
return normalizeCanonical(parsed.data);
|
|
3315
|
+
}
|
|
3316
|
+
function convertTable(table) {
|
|
3317
|
+
const columns = Object.entries(table.columns ?? {});
|
|
3318
|
+
const primaryKey2 = columns.filter(([, column]) => column.primary_key).map(([name]) => name);
|
|
3319
|
+
return {
|
|
3320
|
+
table: table.table,
|
|
3321
|
+
description: table.description,
|
|
3322
|
+
source: table.source,
|
|
3323
|
+
columns: columns.map(([name, column]) => convertColumn(name, column)),
|
|
3324
|
+
// Synced tables reflect their source — no columns, no primary key.
|
|
3325
|
+
primaryKey: primaryKey2.length > 0 ? primaryKey2 : void 0,
|
|
3326
|
+
links: Object.entries(table.links ?? {}).map(([name, link]) => convertLink(table.table, name, link)),
|
|
3327
|
+
indexes: (table.indexes ?? []).map((index3) => ({
|
|
3328
|
+
columns: [...index3.columns],
|
|
3329
|
+
unique: index3.unique ?? false,
|
|
3330
|
+
name: index3.name
|
|
3331
|
+
})),
|
|
3332
|
+
policies: {
|
|
3333
|
+
// Verbatim DSL source strings — the parsed v1 AST is derived state.
|
|
3334
|
+
read: table.policies?.read?.source,
|
|
3335
|
+
write: table.policies?.write?.source
|
|
3336
|
+
},
|
|
3337
|
+
actions: Object.entries(table.actions ?? {}).map(([name, action]) => convertAction(table.table, name, action))
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
function convertColumn(name, column) {
|
|
3341
|
+
const isDefaultFn = column.default !== void 0 && isFunctionDefault(column.default, column.type);
|
|
3342
|
+
return {
|
|
3343
|
+
name,
|
|
3344
|
+
columnName: name,
|
|
3345
|
+
type: column.type,
|
|
3346
|
+
nullable: column.primary_key ? false : column.nullable !== false,
|
|
3347
|
+
unique: column.unique === true,
|
|
3348
|
+
default: isDefaultFn ? void 0 : column.default,
|
|
3349
|
+
// isFunctionDefault guarantees one of the two allowlisted functions; the
|
|
3350
|
+
// canonicalDefinitionSchema parse below re-checks the enum.
|
|
3351
|
+
defaultFn: isDefaultFn ? column.default : void 0,
|
|
3352
|
+
description: column.description,
|
|
3353
|
+
renamedFrom: column.renamed_from,
|
|
3354
|
+
cast: column.cast,
|
|
3355
|
+
deprecated: column.deprecated === true
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
function convertLink(table, name, link) {
|
|
3359
|
+
if (link.to.includes(".")) {
|
|
3360
|
+
throw new OrmDefinitionError(
|
|
3361
|
+
`link '${name}' on '${table}' targets '${link.to}' \u2014 cross-namespace links have no canonical v2 representation (links are namespace-local in v2)`
|
|
3362
|
+
);
|
|
3363
|
+
}
|
|
3364
|
+
return {
|
|
3365
|
+
name,
|
|
3366
|
+
kind: "toOne",
|
|
3367
|
+
target: link.to,
|
|
3368
|
+
on: link.on,
|
|
3369
|
+
enforce: link.enforce === true,
|
|
3370
|
+
description: link.description
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3373
|
+
function convertAction(table, name, action) {
|
|
3374
|
+
return {
|
|
3375
|
+
name,
|
|
3376
|
+
requires: action.requires?.source,
|
|
3377
|
+
params: Object.fromEntries(
|
|
3378
|
+
Object.entries(action.params ?? {}).map(([param, definition]) => [
|
|
3379
|
+
param,
|
|
3380
|
+
{ type: definition.type, nullable: definition.optional === true }
|
|
3381
|
+
])
|
|
3382
|
+
),
|
|
3383
|
+
// The v2 contract carries one SQL string; statement boundaries survive as
|
|
3384
|
+
// `;\n` (v1 validation guarantees no bare `;` inside a single statement).
|
|
3385
|
+
executes: actionStatementSources(action.executes).join(";\n"),
|
|
3386
|
+
// v1 declared the RETURNING columns inline; v2 types them at compile time,
|
|
3387
|
+
// so the shape collapses to rows-vs-rowsAffected.
|
|
3388
|
+
returns: action.returns ? "rows" : "rowsAffected",
|
|
3389
|
+
audit: action.audit ? { action: action.audit.action, resource: action.audit.resource ?? table } : void 0
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
// src/migrations/v2.ts
|
|
3394
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
3395
|
+
function rebaseViolations(statements, options) {
|
|
3396
|
+
const allowSecurity = options?.allowSecurityReemission ?? false;
|
|
3397
|
+
return statements.filter((statement) => !(statement.kind === "security" && allowSecurity));
|
|
3398
|
+
}
|
|
3399
|
+
var verifyRebaseIsNoop = (plan, options) => rebaseViolations(plan.statements, options).length === 0;
|
|
3400
|
+
function buildRebaseJournalEntry({ namespace, previousHash, newHash, ordinal, snapshot }) {
|
|
3401
|
+
if (!SHA256_HEX.test(previousHash)) {
|
|
3402
|
+
throw new OrmDefinitionError(`rebase previousHash is not a sha256 hex digest: '${previousHash}'`);
|
|
3403
|
+
}
|
|
3404
|
+
if (!SHA256_HEX.test(newHash)) {
|
|
3405
|
+
throw new OrmDefinitionError(`rebase newHash is not a sha256 hex digest: '${newHash}'`);
|
|
3406
|
+
}
|
|
3407
|
+
if (previousHash === newHash) {
|
|
3408
|
+
throw new OrmDefinitionError("rebase previousHash and newHash are identical \u2014 nothing to rebase");
|
|
3409
|
+
}
|
|
3410
|
+
if (!Number.isInteger(ordinal) || ordinal < 2) {
|
|
3411
|
+
throw new OrmDefinitionError(`rebase ordinal must be an integer \u2265 2 (a rebase sits on an existing journal tip); got ${ordinal}`);
|
|
3412
|
+
}
|
|
3413
|
+
const name = migrationName(ordinal, newHash);
|
|
3414
|
+
const sql3 = renderRebaseMigrationSql({ name, namespace, previousHash, newHash });
|
|
3415
|
+
const snapshotText = `${JSON.stringify(snapshot, null, 2)}
|
|
3416
|
+
`;
|
|
3417
|
+
const entry = {
|
|
3418
|
+
ordinal,
|
|
3419
|
+
name,
|
|
3420
|
+
content_hash: newHash,
|
|
3421
|
+
previous_content_hash: previousHash,
|
|
3422
|
+
checksum: checksumOf(sql3),
|
|
3423
|
+
snapshot_checksum: checksumOf(snapshotText),
|
|
3424
|
+
destructive: false
|
|
3425
|
+
};
|
|
3426
|
+
return { name, entry, sql: sql3, snapshotText };
|
|
3427
|
+
}
|
|
3428
|
+
function renderRebaseMigrationSql({
|
|
3429
|
+
name,
|
|
3430
|
+
namespace,
|
|
3431
|
+
previousHash,
|
|
3432
|
+
newHash
|
|
3433
|
+
}) {
|
|
3434
|
+
return [
|
|
3435
|
+
`-- ${name}`,
|
|
3436
|
+
`-- namespace: ${namespace}`,
|
|
3437
|
+
`-- content hash: ${newHash}`,
|
|
3438
|
+
`-- rebase: v1 definitions (${previousHash}) converted to canonical v2 \u2014 verified no-op, zero statements`,
|
|
3439
|
+
"-- generated by `seq-studio orm diff --rebase` \u2014 review and commit, do not edit"
|
|
3440
|
+
].join("\n") + "\n";
|
|
3441
|
+
}
|
|
2123
3442
|
|
|
2124
3443
|
// src/schema/canonical.ts
|
|
2125
3444
|
function parseCanonicalDefinition(value) {
|
|
2126
3445
|
const canonical = asCanonicalShape(value);
|
|
2127
|
-
const files = canonical.tables.map((table,
|
|
3446
|
+
const files = canonical.tables.map((table, index3) => {
|
|
2128
3447
|
const input = {
|
|
2129
3448
|
namespace: canonical.namespace,
|
|
2130
3449
|
table: table["table"],
|
|
@@ -2141,10 +3460,10 @@ function parseCanonicalDefinition(value) {
|
|
|
2141
3460
|
const issue = parsed.error.issues[0];
|
|
2142
3461
|
throw new OrmDefinitionError(
|
|
2143
3462
|
`stored definition for table ${String(table["table"])} is invalid: ${issue?.message ?? "unknown issue"}`,
|
|
2144
|
-
`canonical[${
|
|
3463
|
+
`canonical[${index3}]`
|
|
2145
3464
|
);
|
|
2146
3465
|
}
|
|
2147
|
-
return { file: `canonical[${
|
|
3466
|
+
return { file: `canonical[${index3}]`, table: parsed.data };
|
|
2148
3467
|
});
|
|
2149
3468
|
const config = namespaceConfigSchema.safeParse({
|
|
2150
3469
|
namespace: canonical.namespace,
|
|
@@ -2201,22 +3520,56 @@ function canonicalActions(value) {
|
|
|
2201
3520
|
);
|
|
2202
3521
|
}
|
|
2203
3522
|
|
|
3523
|
+
Object.defineProperty(exports, "capabilityPinsSchema", {
|
|
3524
|
+
enumerable: true,
|
|
3525
|
+
get: function () { return customRolesManifest.capabilityPinsSchema; }
|
|
3526
|
+
});
|
|
3527
|
+
Object.defineProperty(exports, "customRolesBlockSchema", {
|
|
3528
|
+
enumerable: true,
|
|
3529
|
+
get: function () { return customRolesManifest.customRolesBlockSchema; }
|
|
3530
|
+
});
|
|
3531
|
+
Object.defineProperty(exports, "customRolesDeclared", {
|
|
3532
|
+
enumerable: true,
|
|
3533
|
+
get: function () { return customRolesManifest.customRolesDeclared; }
|
|
3534
|
+
});
|
|
3535
|
+
Object.defineProperty(exports, "parseCapabilityPin", {
|
|
3536
|
+
enumerable: true,
|
|
3537
|
+
get: function () { return customRolesManifest.parseCapabilityPin; }
|
|
3538
|
+
});
|
|
3539
|
+
Object.defineProperty(exports, "validateCapabilityPins", {
|
|
3540
|
+
enumerable: true,
|
|
3541
|
+
get: function () { return customRolesManifest.validateCapabilityPins; }
|
|
3542
|
+
});
|
|
2204
3543
|
exports.ACTION_REQUIRES_DENIED_SQLSTATE = ACTION_REQUIRES_DENIED_SQLSTATE;
|
|
2205
3544
|
exports.ACTION_VALIDATES_FAILED_SQLSTATE = ACTION_VALIDATES_FAILED_SQLSTATE;
|
|
2206
3545
|
exports.AUDIT_ACTIONS = AUDIT_ACTIONS;
|
|
3546
|
+
exports.CANONICAL_FORMAT = CANONICAL_FORMAT;
|
|
2207
3547
|
exports.CAPABILITIES_CLAIM_KEY = CAPABILITIES_CLAIM_KEY;
|
|
2208
3548
|
exports.CAPABILITY_KEY_PATTERN = CAPABILITY_KEY_PATTERN;
|
|
3549
|
+
exports.CLAIMS_CONTRACT = CLAIMS_CONTRACT;
|
|
2209
3550
|
exports.CLAIMS_SETTING = CLAIMS_SETTING;
|
|
2210
3551
|
exports.CLAIM_KEY_PATTERN = CLAIM_KEY_PATTERN;
|
|
2211
|
-
exports.
|
|
3552
|
+
exports.CMP_OPS = CMP_OPS;
|
|
3553
|
+
exports.COLUMN_BRAND = COLUMN_BRAND;
|
|
3554
|
+
exports.COLUMN_DEFAULT_FNS = COLUMN_DEFAULT_FNS;
|
|
3555
|
+
exports.COLUMN_TYPES = COLUMN_TYPES2;
|
|
2212
3556
|
exports.COMPARE_OPS = COMPARE_OPS;
|
|
2213
3557
|
exports.CONSUMER_ROLE_SUFFIX = CONSUMER_ROLE_SUFFIX;
|
|
3558
|
+
exports.ColumnBuilder = ColumnBuilder;
|
|
2214
3559
|
exports.DEFINITION_FILE_EXTENSIONS = DEFINITION_FILE_EXTENSIONS;
|
|
3560
|
+
exports.ENUM_BRAND = ENUM_BRAND;
|
|
2215
3561
|
exports.HASH_DOMAIN = HASH_DOMAIN;
|
|
2216
3562
|
exports.IDENTIFIER_PATTERN = IDENTIFIER_PATTERN;
|
|
3563
|
+
exports.IDENTIFIER_RE = IDENTIFIER_RE;
|
|
3564
|
+
exports.INPUT_REF_KINDS = INPUT_REF_KINDS;
|
|
3565
|
+
exports.IR_VERSION = IR_VERSION;
|
|
3566
|
+
exports.IrVersionError = IrVersionError;
|
|
3567
|
+
exports.LINK_BRAND = LINK_BRAND;
|
|
2217
3568
|
exports.MAX_DEFINITION_FILE_BYTES = MAX_DEFINITION_FILE_BYTES;
|
|
2218
3569
|
exports.MAX_IDENTIFIER_LENGTH = MAX_IDENTIFIER_LENGTH;
|
|
2219
3570
|
exports.MIGRATIONS_DIRNAME = MIGRATIONS_DIRNAME;
|
|
3571
|
+
exports.NAMESPACE_CONFIG_BRAND = NAMESPACE_CONFIG_BRAND;
|
|
3572
|
+
exports.NAMESPACE_CONFIG_FILE = NAMESPACE_CONFIG_FILE;
|
|
2220
3573
|
exports.NAMESPACE_CONFIG_FILENAMES = NAMESPACE_CONFIG_FILENAMES;
|
|
2221
3574
|
exports.NAMESPACE_PATTERN = NAMESPACE_PATTERN;
|
|
2222
3575
|
exports.ORM_DATA_TOKEN_HEADER = ORM_DATA_TOKEN_HEADER;
|
|
@@ -2227,8 +3580,10 @@ exports.PredicateParseError = PredicateParseError;
|
|
|
2227
3580
|
exports.QUALIFIED_TABLE_PATTERN = QUALIFIED_TABLE_PATTERN;
|
|
2228
3581
|
exports.RESERVED_COLUMN_NAMES = RESERVED_COLUMN_NAMES;
|
|
2229
3582
|
exports.RESERVED_TABLE_NAMES = RESERVED_TABLE_NAMES;
|
|
3583
|
+
exports.SCHEMA_DIR = SCHEMA_DIR;
|
|
2230
3584
|
exports.STATEMENT_KINDS = STATEMENT_KINDS;
|
|
2231
3585
|
exports.SYNCED_SOURCE_PATTERN = SYNCED_SOURCE_PATTERN;
|
|
3586
|
+
exports.TABLE_BRAND = TABLE_BRAND;
|
|
2232
3587
|
exports.actionFunctionName = actionFunctionName;
|
|
2233
3588
|
exports.actionFunctionStatements = actionFunctionStatements;
|
|
2234
3589
|
exports.actionParamArg = actionParamArg;
|
|
@@ -2236,72 +3591,120 @@ exports.actionParamOrder = actionParamOrder;
|
|
|
2236
3591
|
exports.actionParamSchema = actionParamSchema;
|
|
2237
3592
|
exports.actionPrepareChecks = actionPrepareChecks;
|
|
2238
3593
|
exports.actionSchema = actionSchema;
|
|
3594
|
+
exports.actionWriteTargets = actionWriteTargets;
|
|
2239
3595
|
exports.actionsInterfaceName = actionsInterfaceName;
|
|
3596
|
+
exports.activeAuthoringContext = activeAuthoringContext;
|
|
2240
3597
|
exports.applyMigrationHints = applyMigrationHints;
|
|
3598
|
+
exports.applyMigrationHintsV2 = applyMigrationHintsV2;
|
|
3599
|
+
exports.beginAuthoringContext = beginAuthoringContext;
|
|
2241
3600
|
exports.buildActionParamsSchema = buildActionParamsSchema;
|
|
2242
3601
|
exports.buildDrizzleEntities = buildDrizzleEntities;
|
|
3602
|
+
exports.buildDrizzleEntitiesV2 = buildDrizzleEntitiesV2;
|
|
2243
3603
|
exports.buildNamespaceDefinition = buildNamespaceDefinition;
|
|
3604
|
+
exports.buildRebaseJournalEntry = buildRebaseJournalEntry;
|
|
3605
|
+
exports.canonicalActionSchema = canonicalActionSchema;
|
|
3606
|
+
exports.canonicalColumnSchema = canonicalColumnSchema;
|
|
3607
|
+
exports.canonicalDefinitionSchema = canonicalDefinitionSchema;
|
|
3608
|
+
exports.canonicalEnumSchema = canonicalEnumSchema;
|
|
3609
|
+
exports.canonicalIndexSchema = canonicalIndexSchema;
|
|
3610
|
+
exports.canonicalLinkSchema = canonicalLinkSchema;
|
|
3611
|
+
exports.canonicalPoliciesSchema = canonicalPoliciesSchema;
|
|
3612
|
+
exports.canonicalTableSchema = canonicalTableSchema;
|
|
2244
3613
|
exports.canonicalizeDefinition = canonicalizeDefinition;
|
|
2245
3614
|
exports.capabilityExpression = capabilityExpression;
|
|
2246
|
-
exports.capabilityPinsSchema = capabilityPinsSchema;
|
|
2247
3615
|
exports.castParts = castParts;
|
|
2248
3616
|
exports.checksumOf = checksumOf;
|
|
2249
3617
|
exports.claimExpression = claimExpression;
|
|
2250
3618
|
exports.classifyDestructive = classifyDestructive;
|
|
2251
3619
|
exports.columnSchema = columnSchema;
|
|
2252
|
-
exports.columnTypeSchema =
|
|
3620
|
+
exports.columnTypeSchema = columnTypeSchema2;
|
|
2253
3621
|
exports.compileActionRequires = compileActionRequires;
|
|
2254
3622
|
exports.compileActionStatements = compileActionStatements;
|
|
2255
3623
|
exports.compileNamespace = compileNamespace;
|
|
2256
3624
|
exports.compilePredicate = compilePredicate;
|
|
3625
|
+
exports.compiledArtifactSchema = compiledArtifactSchema;
|
|
3626
|
+
exports.compiledOperationSchema = compiledOperationSchema;
|
|
3627
|
+
exports.computeCanonicalHash = computeCanonicalHash;
|
|
2257
3628
|
exports.computeDefinitionHash = computeDefinitionHash;
|
|
2258
3629
|
exports.consumerRoleName = consumerRoleName;
|
|
2259
|
-
exports.
|
|
2260
|
-
exports.customRolesDeclared = customRolesDeclared;
|
|
3630
|
+
exports.convertV1DefinitionToCanonical = convertV1DefinitionToCanonical;
|
|
2261
3631
|
exports.declaredCapabilityKeys = declaredCapabilityKeys;
|
|
3632
|
+
exports.defineEnum = defineEnum;
|
|
3633
|
+
exports.defineNamespace = defineNamespace;
|
|
3634
|
+
exports.defineSyncedTable = defineSyncedTable;
|
|
3635
|
+
exports.defineTable = defineTable;
|
|
3636
|
+
exports.deriveColumnName = deriveColumnName;
|
|
2262
3637
|
exports.diffCommand = diffCommand;
|
|
2263
3638
|
exports.diffStatements = diffStatements;
|
|
2264
3639
|
exports.emptyJournal = emptyJournal;
|
|
2265
3640
|
exports.emptySnapshot = emptySnapshot;
|
|
3641
|
+
exports.endAuthoringContext = endAuthoringContext;
|
|
2266
3642
|
exports.extractNamedParams = extractNamedParams;
|
|
2267
3643
|
exports.generateNamespaceTypes = generateNamespaceTypes;
|
|
2268
3644
|
exports.generateSnapshot = generateSnapshot;
|
|
3645
|
+
exports.generateSnapshotV2 = generateSnapshotV2;
|
|
2269
3646
|
exports.identifierSchema = identifierSchema;
|
|
3647
|
+
exports.indexByHash = indexByHash;
|
|
3648
|
+
exports.isColumnBuilder = isColumnBuilder;
|
|
3649
|
+
exports.isEnumRef = isEnumRef;
|
|
2270
3650
|
exports.isFunctionDefault = isFunctionDefault;
|
|
2271
3651
|
exports.isOwnedStatement = isOwnedStatement;
|
|
2272
3652
|
exports.isQualifiedReference = isQualifiedReference;
|
|
3653
|
+
exports.isSeqLink = isSeqLink;
|
|
3654
|
+
exports.isSeqNamespaceConfig = isSeqNamespaceConfig;
|
|
3655
|
+
exports.isSeqTable = isSeqTable;
|
|
3656
|
+
exports.limitIrSchema = limitIrSchema;
|
|
2273
3657
|
exports.loadCommittedMigrations = loadCommittedMigrations;
|
|
2274
3658
|
exports.loadCommittedTipMigration = loadCommittedTipMigration;
|
|
2275
3659
|
exports.loadNamespaceDefinition = loadNamespaceDefinition;
|
|
3660
|
+
exports.loadNamespaceFromDir = loadNamespaceFromDir;
|
|
2276
3661
|
exports.migrationName = migrationName;
|
|
2277
3662
|
exports.namespaceConfigSchema = namespaceConfigSchema;
|
|
2278
3663
|
exports.namespaceSchema = namespaceSchema;
|
|
3664
|
+
exports.normalizeCanonical = normalizeCanonical;
|
|
3665
|
+
exports.normalizeIr = normalizeIr;
|
|
2279
3666
|
exports.normalizeSnapshot = normalizeSnapshot;
|
|
3667
|
+
exports.orderIrSchema = orderIrSchema;
|
|
2280
3668
|
exports.ormSchemaName = ormSchemaName;
|
|
2281
3669
|
exports.parseCanonicalDefinition = parseCanonicalDefinition;
|
|
2282
|
-
exports.parseCapabilityPin = parseCapabilityPin;
|
|
2283
3670
|
exports.parseDefinitionSources = parseDefinitionSources;
|
|
2284
3671
|
exports.parsePredicate = parsePredicate;
|
|
2285
3672
|
exports.parseTableFile = parseTableFile;
|
|
2286
3673
|
exports.parsedPredicateSchema = parsedPredicateSchema;
|
|
2287
3674
|
exports.pendingKitStatements = pendingKitStatements;
|
|
2288
3675
|
exports.planMigration = planMigration;
|
|
3676
|
+
exports.planMigrationV2 = planMigrationV2;
|
|
3677
|
+
exports.policyExpressionSchema = policyExpressionSchema;
|
|
3678
|
+
exports.policyVariantSchema = policyVariantSchema;
|
|
3679
|
+
exports.predIrSchema = predIrSchema;
|
|
2289
3680
|
exports.predicateOperands = predicateOperands;
|
|
2290
3681
|
exports.quoteIdentifier = quoteIdentifier;
|
|
2291
3682
|
exports.quoteLiteral = quoteLiteral;
|
|
2292
3683
|
exports.readMigrationSql = readMigrationSql;
|
|
2293
3684
|
exports.readMigrationWorkspace = readMigrationWorkspace;
|
|
2294
3685
|
exports.readSnapshot = readSnapshot;
|
|
3686
|
+
exports.rebaseViolations = rebaseViolations;
|
|
2295
3687
|
exports.referencedCapabilities = referencedCapabilities;
|
|
2296
3688
|
exports.referencedColumns = referencedColumns;
|
|
2297
3689
|
exports.renderMigrationSql = renderMigrationSql;
|
|
3690
|
+
exports.rootIrSchema = rootIrSchema;
|
|
2298
3691
|
exports.securityStatements = securityStatements;
|
|
3692
|
+
exports.selectionIrSchema = selectionIrSchema;
|
|
3693
|
+
exports.serializeNamespace = serializeNamespace;
|
|
2299
3694
|
exports.sqlTypeName = sqlTypeName;
|
|
2300
3695
|
exports.stableJson = stableJson;
|
|
3696
|
+
exports.t = t;
|
|
2301
3697
|
exports.tableFileSchema = tableFileSchema;
|
|
3698
|
+
exports.tableMetaIrSchema = tableMetaIrSchema;
|
|
2302
3699
|
exports.tableReferenceSchema = tableReferenceSchema;
|
|
3700
|
+
exports.toMany = toMany;
|
|
3701
|
+
exports.toOne = toOne;
|
|
3702
|
+
exports.toPlatformUser = toPlatformUser;
|
|
2303
3703
|
exports.typesInterfaceName = typesInterfaceName;
|
|
2304
|
-
exports.
|
|
3704
|
+
exports.valueIrSchema = valueIrSchema;
|
|
3705
|
+
exports.variableDefaultIrSchema = variableDefaultIrSchema;
|
|
3706
|
+
exports.variableIrSchema = variableIrSchema;
|
|
3707
|
+
exports.verifyRebaseIsNoop = verifyRebaseIsNoop;
|
|
2305
3708
|
exports.walkPredicate = walkPredicate;
|
|
2306
3709
|
exports.writeMigration = writeMigration;
|
|
2307
3710
|
//# sourceMappingURL=index.cjs.map
|