@sequenceholdings/orm 2.4.0 → 2.5.0
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/dist/authoring.cjs +53 -7
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +11 -7
- package/dist/authoring.d.ts +11 -7
- package/dist/authoring.js +53 -7
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +48 -2
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +72 -72
- package/dist/canonical.d.ts +72 -72
- package/dist/canonical.js +48 -2
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +237 -20
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +237 -20
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +386 -14
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +16 -3
- package/dist/compiler.d.ts +16 -3
- package/dist/compiler.js +386 -15
- package/dist/compiler.js.map +1 -1
- package/dist/{constants-C6EG6Wq7.d.cts → constants-Dou1RkTo.d.cts} +7 -2
- package/dist/{constants-C6EG6Wq7.d.ts → constants-Dou1RkTo.d.ts} +7 -2
- package/dist/dynamic-input.d.cts +1 -1
- package/dist/dynamic-input.d.ts +1 -1
- package/dist/index.cjs +329 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -58
- package/dist/index.d.ts +66 -58
- package/dist/index.js +329 -131
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +50 -3
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +71 -71
- package/dist/ir.d.ts +71 -71
- package/dist/ir.js +50 -3
- package/dist/ir.js.map +1 -1
- package/dist/predicate-v2.cjs +50 -3
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.d.cts +1 -1
- package/dist/predicate-v2.d.ts +1 -1
- package/dist/predicate-v2.js +50 -3
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +53 -3
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +3 -1
- package/dist/sdl.d.ts +3 -1
- package/dist/sdl.js +53 -3
- package/dist/sdl.js.map +1 -1
- package/package.json +1 -1
- package/dist/{definition-DjfC7Lll.d.cts → definition-B99BsOAO.d.cts} +4 -4
- package/dist/{definition-DjfC7Lll.d.ts → definition-B99BsOAO.d.ts} +4 -4
package/dist/cli.js
CHANGED
|
@@ -56,18 +56,22 @@ var init_rehearsal_poll = __esm({
|
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
// src/constants.ts
|
|
59
|
-
var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
|
|
59
|
+
var NAMESPACE_PATTERN = /^(?!(?:events|native_sources)$)[a-z][a-z0-9_]{0,40}$/;
|
|
60
60
|
var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
61
61
|
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
62
62
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
63
63
|
var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
|
|
64
|
+
var MAX_PERSISTED_READ_ROWS = 50001;
|
|
64
65
|
var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
65
66
|
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
|
|
66
67
|
var SYNCED_SOURCE_PATTERN = QUALIFIED_TABLE_PATTERN;
|
|
67
68
|
var ORM_SCHEMA_PREFIX = "orm_";
|
|
68
69
|
var CONSUMER_ROLE_SUFFIX = "_consumer";
|
|
69
|
-
var ormSchemaName = (namespace) =>
|
|
70
|
-
|
|
70
|
+
var ormSchemaName = (namespace) => {
|
|
71
|
+
if (!NAMESPACE_PATTERN.test(namespace)) throw new Error(`invalid or reserved ORM namespace '${namespace}'`);
|
|
72
|
+
return `${ORM_SCHEMA_PREFIX}${namespace}`;
|
|
73
|
+
};
|
|
74
|
+
var consumerRoleName = (namespace) => `${ormSchemaName(namespace)}${CONSUMER_ROLE_SUFFIX}`;
|
|
71
75
|
var DEFINITION_FILE_EXTENSIONS = [".yml", ".yaml"];
|
|
72
76
|
var NAMESPACE_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["namespace.yaml", "namespace.yml"]);
|
|
73
77
|
var HASH_DOMAIN = "sequence-orm-definitions\0";
|
|
@@ -535,6 +539,7 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
535
539
|
function validateSource({ scope, source, table }) {
|
|
536
540
|
const { fail: fail4 } = scope;
|
|
537
541
|
if (source.kind === "etl" && table.source === void 0) fail4(`etl source '${source.name}' must reference a synced table`);
|
|
542
|
+
if (source.kind === "etl" && table.primaryKey?.length === 0) fail4(`etl source '${source.name}' cannot use an unkeyed synced table for resolved row identity`);
|
|
538
543
|
if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
|
|
539
544
|
fail4(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
|
|
540
545
|
}
|
|
@@ -983,7 +988,7 @@ var canonicalTableSchema = z.object({
|
|
|
983
988
|
/** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
|
|
984
989
|
source: z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
|
|
985
990
|
columns: z.array(canonicalColumnSchema).default([]),
|
|
986
|
-
primaryKey: z.array(columnIdentifier).
|
|
991
|
+
primaryKey: z.array(columnIdentifier).max(8).optional(),
|
|
987
992
|
links: z.array(canonicalLinkSchema).default([]),
|
|
988
993
|
indexes: z.array(canonicalIndexSchema).default([]),
|
|
989
994
|
/** Omitted by old definitions, preserving their existing content hashes. */
|
|
@@ -1019,6 +1024,29 @@ var canonicalTableSchema = z.object({
|
|
|
1019
1024
|
* data plane. Omitted otherwise.
|
|
1020
1025
|
*/
|
|
1021
1026
|
materializes: identifier2.optional()
|
|
1027
|
+
}).superRefine((table, ctx) => {
|
|
1028
|
+
if (table.primaryKey?.length !== 0) return;
|
|
1029
|
+
const fail4 = (message, path = ["primaryKey"]) => ctx.addIssue({ code: "custom", path, message: `unkeyed table '${table.table}' ${message}` });
|
|
1030
|
+
if (table.source === void 0 || table.columns.length === 0) {
|
|
1031
|
+
fail4("requires a typed synced source");
|
|
1032
|
+
}
|
|
1033
|
+
if ([table.policies.write, table.policies.insert, table.policies.update].some((policy) => policy !== void 0)) {
|
|
1034
|
+
fail4("is read-only and cannot declare write policies", ["policies"]);
|
|
1035
|
+
}
|
|
1036
|
+
const ownedCollections = [table.indexes, table.actions, table.checks, table.foreignKeys, table.cowMutations];
|
|
1037
|
+
const ownedMetadata = [table.cellMetadata, table.internal, table.materializes, table.renamedFrom];
|
|
1038
|
+
if (ownedCollections.some((items) => items !== void 0 && items.length > 0) || ownedMetadata.some((value) => value !== void 0)) {
|
|
1039
|
+
fail4("cannot declare owned storage, constraints or identity operations");
|
|
1040
|
+
}
|
|
1041
|
+
for (const [index3, column] of table.columns.entries()) {
|
|
1042
|
+
const ownedColumnMetadata = [column.default, column.defaultFn, column.onUpdateFn, column.renamedFrom, column.cast, column.cell];
|
|
1043
|
+
if (column.unique || ownedColumnMetadata.some((value) => value !== void 0)) {
|
|
1044
|
+
fail4("cannot declare column uniqueness, mutation defaults or migration metadata", ["columns", index3]);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
for (const [index3, link] of table.links.entries()) {
|
|
1048
|
+
if (link.enforce || link.kind === "toMany") fail4("cannot provide an identity for this link", ["links", index3]);
|
|
1049
|
+
}
|
|
1022
1050
|
});
|
|
1023
1051
|
var MAX_VIEW_QUERY = 1e5;
|
|
1024
1052
|
var MAX_RESOLVED_VIEW_QUERY = 5e6;
|
|
@@ -1075,6 +1103,26 @@ var canonicalViewSchema = z.object({
|
|
|
1075
1103
|
}
|
|
1076
1104
|
}
|
|
1077
1105
|
});
|
|
1106
|
+
function validateUnkeyedLinkIdentities({ definition, ctx }) {
|
|
1107
|
+
const tables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1108
|
+
const relations = [
|
|
1109
|
+
...definition.tables.map((table, index3) => ({ kind: "tables", index: index3, links: table.links })),
|
|
1110
|
+
...(definition.views ?? []).map((view, index3) => ({ kind: "views", index: index3, links: view.links }))
|
|
1111
|
+
];
|
|
1112
|
+
for (const relation of relations) {
|
|
1113
|
+
for (const [linkIndex, link] of relation.links.entries()) {
|
|
1114
|
+
if (link.kind !== "toOne" || link.namespace !== void 0 && link.namespace !== definition.namespace) continue;
|
|
1115
|
+
const target = link.target === void 0 ? void 0 : tables.get(link.target);
|
|
1116
|
+
if (target?.source !== void 0 && target.primaryKey?.length === 0) {
|
|
1117
|
+
ctx.addIssue({
|
|
1118
|
+
code: "custom",
|
|
1119
|
+
path: [relation.kind, relation.index, "links", linkIndex],
|
|
1120
|
+
message: `link '${link.name}' cannot use unkeyed table '${target.table}' as a to-one identity`
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1078
1126
|
var CELL_PART_TYPES = {
|
|
1079
1127
|
at: "timestamptz",
|
|
1080
1128
|
by: "text",
|
|
@@ -1138,7 +1186,7 @@ var exportGateSchema = z.union([z.literal(true), z.object({ capability: z.string
|
|
|
1138
1186
|
var relationExportSchema = z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
|
|
1139
1187
|
var localDefinitionSchema = z.object({
|
|
1140
1188
|
definition_format: z.literal(CANONICAL_FORMAT),
|
|
1141
|
-
namespace: identifier2.max(41),
|
|
1189
|
+
namespace: identifier2.max(41).regex(NAMESPACE_PATTERN, "invalid or reserved ORM namespace"),
|
|
1142
1190
|
description: z.string().max(2e3).optional(),
|
|
1143
1191
|
enums: z.array(canonicalEnumSchema).default([]),
|
|
1144
1192
|
tables: z.array(canonicalTableSchema).min(0).max(500),
|
|
@@ -1172,6 +1220,7 @@ var localDefinitionSchema = z.object({
|
|
|
1172
1220
|
}
|
|
1173
1221
|
}
|
|
1174
1222
|
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1223
|
+
validateUnkeyedLinkIdentities({ definition, ctx });
|
|
1175
1224
|
const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
|
|
1176
1225
|
for (const name2 of Object.keys(definition.exports ?? {})) {
|
|
1177
1226
|
if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
|
|
@@ -1494,7 +1543,7 @@ var limitIrSchema = z.object({
|
|
|
1494
1543
|
variable: z.string().optional(),
|
|
1495
1544
|
value: z.number().int().positive().optional(),
|
|
1496
1545
|
default: z.number().int().positive(),
|
|
1497
|
-
max: z.number().int().positive()
|
|
1546
|
+
max: z.number().int().positive().max(MAX_PERSISTED_READ_ROWS)
|
|
1498
1547
|
});
|
|
1499
1548
|
var selectionIrSchema = z.lazy(
|
|
1500
1549
|
() => z.object({
|
|
@@ -3118,6 +3167,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
3118
3167
|
const platformMeta = usesPlatformUsers ? platformUserMeta() : void 0;
|
|
3119
3168
|
if (platformMeta) tables[PLATFORM_USER_TABLE] = platformMeta;
|
|
3120
3169
|
const syncedTableNames = new Set(canonical.tables.filter((t2) => t2.source !== void 0).map((t2) => t2.table));
|
|
3170
|
+
const unkeyedSyncedTableNames = new Set(relations.filter((table) => table.source !== void 0 && table.primaryKey?.length === 0).map((table) => table.table));
|
|
3121
3171
|
const viewDependencies = new Map((canonical.views ?? []).map((view) => [view.view, view.dependencies]));
|
|
3122
3172
|
const claimsGatedMemo = /* @__PURE__ */ new Map();
|
|
3123
3173
|
const relationIsClaimsGated = (name2) => {
|
|
@@ -3131,6 +3181,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
3131
3181
|
};
|
|
3132
3182
|
for (const table of relations) {
|
|
3133
3183
|
const isView = viewNames.has(table.table);
|
|
3184
|
+
const readOnly = isView || unkeyedSyncedTableNames.has(table.table);
|
|
3134
3185
|
const owner = owners.get(table.table);
|
|
3135
3186
|
const entries = resolveColumnEntries(table, enums, diagnostics);
|
|
3136
3187
|
columnEntries.set(table.table, entries);
|
|
@@ -3178,13 +3229,13 @@ function buildCompileContext(rawCanonical) {
|
|
|
3178
3229
|
// are claims-gated — see tableMetaIrSchema.claimsGated.
|
|
3179
3230
|
claimsGated: relationIsClaimsGated(table.table),
|
|
3180
3231
|
columns: entries.map(({ deprecated: _deprecated, onUpdateFn: _onUpdateFn, ...column }) => column),
|
|
3181
|
-
primaryKey: isView ? primaryKey2 : primaryKey2.length > 0 ? primaryKey2 : ["id"],
|
|
3232
|
+
primaryKey: isView || unkeyedSyncedTableNames.has(table.table) ? primaryKey2 : primaryKey2.length > 0 ? primaryKey2 : ["id"],
|
|
3182
3233
|
links: [],
|
|
3183
3234
|
filterable: data.map((c) => c.name),
|
|
3184
3235
|
orderable: data.filter((c) => c.type !== "jsonb").map((c) => c.name),
|
|
3185
|
-
insertable: resolved ? resolvedWritable(data) :
|
|
3186
|
-
settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey2.includes(name2)) :
|
|
3187
|
-
appendable:
|
|
3236
|
+
insertable: resolved ? resolvedWritable(data) : readOnly ? [] : active.map((c) => c.name),
|
|
3237
|
+
settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey2.includes(name2)) : readOnly ? [] : active.filter((c) => !c.onUpdateFn).map((c) => c.name),
|
|
3238
|
+
appendable: readOnly ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
|
|
3188
3239
|
...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
|
|
3189
3240
|
...cells ? { cells } : {},
|
|
3190
3241
|
...resolved ? { resolved: resolvedMeta({ resolved, pk: primaryKey2[0] }) } : {},
|
|
@@ -3208,6 +3259,16 @@ function buildCompileContext(rawCanonical) {
|
|
|
3208
3259
|
});
|
|
3209
3260
|
continue;
|
|
3210
3261
|
}
|
|
3262
|
+
const identityTable = link.kind === "toOne" ? targetName : link.kind === "toMany" ? table.table : void 0;
|
|
3263
|
+
if (identityTable !== void 0 && unkeyedSyncedTableNames.has(identityTable)) {
|
|
3264
|
+
diagnostics.push({
|
|
3265
|
+
severity: "error",
|
|
3266
|
+
code: "unkeyed-link-identity",
|
|
3267
|
+
table: table.table,
|
|
3268
|
+
message: `link '${link.name}' cannot use unkeyed table '${identityTable}' as a row identity`
|
|
3269
|
+
});
|
|
3270
|
+
continue;
|
|
3271
|
+
}
|
|
3211
3272
|
const targetEntries = targetName === PLATFORM_USER_TABLE ? target.columns.map((c) => ({ ...c, deprecated: false, onUpdateFn: false })) : columnEntries.get(targetName);
|
|
3212
3273
|
const onEntries = link.kind === "toMany" ? targetEntries : entries;
|
|
3213
3274
|
const referencesEntries = link.kind === "toMany" ? entries : targetEntries;
|
|
@@ -3273,7 +3334,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
3273
3334
|
compilePolicyFor("write", writePolicies);
|
|
3274
3335
|
compilePolicyFor("insert", insertPolicies);
|
|
3275
3336
|
compilePolicyFor("update", updatePolicies);
|
|
3276
|
-
if (isView && table.resolved === void 0) {
|
|
3337
|
+
if (isView && table.resolved === void 0 || unkeyedSyncedTableNames.has(table.table)) {
|
|
3277
3338
|
writePolicies.set(table.table, CONST_FALSE);
|
|
3278
3339
|
insertPolicies.set(table.table, CONST_FALSE);
|
|
3279
3340
|
updatePolicies.set(table.table, CONST_FALSE);
|
|
@@ -3348,7 +3409,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
3348
3409
|
const queryRoots = /* @__PURE__ */ new Map();
|
|
3349
3410
|
const mutationRoots = /* @__PURE__ */ new Map();
|
|
3350
3411
|
for (const table of relations) {
|
|
3351
|
-
if (table.internal === true) continue;
|
|
3412
|
+
if (table.internal === true || unkeyedSyncedTableNames.has(table.table)) continue;
|
|
3352
3413
|
const roots = queryRootNames(table.table);
|
|
3353
3414
|
queryRoots.set(roots.select, { kind: "select", table: table.table });
|
|
3354
3415
|
if ((tables[table.table]?.primaryKey.length ?? 0) > 0) {
|
|
@@ -3728,8 +3789,10 @@ var OperationLowerer = class {
|
|
|
3728
3789
|
constructor(ctx, restricted) {
|
|
3729
3790
|
this.ctx = ctx;
|
|
3730
3791
|
this.variables = new Map(restricted.variables.map((v) => [v.name, v]));
|
|
3792
|
+
this.maxRows = restricted.readBudget ?? MAX_LIMIT;
|
|
3731
3793
|
}
|
|
3732
3794
|
variables;
|
|
3795
|
+
maxRows;
|
|
3733
3796
|
meta(table) {
|
|
3734
3797
|
return this.ctx.tables[table];
|
|
3735
3798
|
}
|
|
@@ -3933,13 +3996,13 @@ var OperationLowerer = class {
|
|
|
3933
3996
|
});
|
|
3934
3997
|
}
|
|
3935
3998
|
lowerLimit(node) {
|
|
3936
|
-
const base2 = { default: DEFAULT_LIMIT, max:
|
|
3999
|
+
const base2 = { default: DEFAULT_LIMIT, max: this.maxRows };
|
|
3937
4000
|
if (!node) return base2;
|
|
3938
4001
|
if (node.kind === Kind.VARIABLE) return { ...base2, variable: node.name.value };
|
|
3939
4002
|
if (node.kind === Kind.INT) {
|
|
3940
4003
|
const value = Number(node.value);
|
|
3941
4004
|
if (value < 1) fail2("limit must be a positive integer", "invalid-limit");
|
|
3942
|
-
return { ...base2, value: Math.min(value,
|
|
4005
|
+
return { ...base2, value: Math.min(value, this.maxRows) };
|
|
3943
4006
|
}
|
|
3944
4007
|
fail2("limit must be an integer literal or a variable", "invalid-limit");
|
|
3945
4008
|
}
|
|
@@ -4738,7 +4801,13 @@ var OperationLowerer = class {
|
|
|
4738
4801
|
};
|
|
4739
4802
|
function lowerOperation({ restricted, ctx }) {
|
|
4740
4803
|
const lowerer = new OperationLowerer(ctx, restricted);
|
|
4741
|
-
|
|
4804
|
+
const roots = lowerer.lowerRoots(restricted.definition, restricted.kind);
|
|
4805
|
+
if (restricted.readBudget === void 0) return roots;
|
|
4806
|
+
const root = roots[0];
|
|
4807
|
+
if (restricted.kind !== "query" || restricted.definition.selectionSet.selections.length !== 1 || roots.length !== 1 || root?.kind !== "select" || root.selection.relations.length > 0 || root.selection.aggregates.length > 0 || root.selection.cells !== void 0 || root.prefetch?.length) {
|
|
4808
|
+
fail2("@readBudget requires one flat select root without related selections, aggregates, cells, or prefetch", "invalid-read-budget");
|
|
4809
|
+
}
|
|
4810
|
+
return roots;
|
|
4742
4811
|
}
|
|
4743
4812
|
function isCellColumn(meta, name2) {
|
|
4744
4813
|
if (!meta.cells) return false;
|
|
@@ -4797,6 +4866,19 @@ function parseDocuments({
|
|
|
4797
4866
|
}
|
|
4798
4867
|
return { documents: parsed, diagnostics };
|
|
4799
4868
|
}
|
|
4869
|
+
function parseReadBudget(operation) {
|
|
4870
|
+
const directives = operation.directives?.filter((directive) => directive.name.value === "readBudget") ?? [];
|
|
4871
|
+
if (directives.length === 0) return void 0;
|
|
4872
|
+
const value = directives[0]?.arguments?.find((argument) => argument.name.value === "maxRows")?.value;
|
|
4873
|
+
if (operation.operation !== "query" || directives.length !== 1 || value?.kind !== Kind.INT) {
|
|
4874
|
+
throw new DocumentCompileError("@readBudget requires a literal maxRows on a query", "invalid-read-budget");
|
|
4875
|
+
}
|
|
4876
|
+
const maxRows = Number(value.value);
|
|
4877
|
+
if (!Number.isSafeInteger(maxRows) || maxRows < 1 || maxRows > MAX_PERSISTED_READ_ROWS) {
|
|
4878
|
+
throw new DocumentCompileError(`@readBudget(maxRows:) must be between 1 and ${MAX_PERSISTED_READ_ROWS}`, "invalid-read-budget");
|
|
4879
|
+
}
|
|
4880
|
+
return maxRows;
|
|
4881
|
+
}
|
|
4800
4882
|
function unwrapType(node, variable) {
|
|
4801
4883
|
let nullable = true;
|
|
4802
4884
|
let list2 = false;
|
|
@@ -4957,6 +5039,7 @@ function restrictOperation({
|
|
|
4957
5039
|
name: name2,
|
|
4958
5040
|
kind: operation.operation === "mutation" ? "mutation" : "query",
|
|
4959
5041
|
requires: parseRequires(operation),
|
|
5042
|
+
readBudget: parseReadBudget(operation),
|
|
4960
5043
|
variables,
|
|
4961
5044
|
definition: operation
|
|
4962
5045
|
};
|
|
@@ -5614,6 +5697,118 @@ function bindNamedParameters({ sql: sql4, names }) {
|
|
|
5614
5697
|
}
|
|
5615
5698
|
return result;
|
|
5616
5699
|
}
|
|
5700
|
+
var parser2;
|
|
5701
|
+
var functions = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
|
|
5702
|
+
var scalarTypes = /* @__PURE__ */ new Set([
|
|
5703
|
+
"bool",
|
|
5704
|
+
"boolean",
|
|
5705
|
+
"int2",
|
|
5706
|
+
"int4",
|
|
5707
|
+
"int8",
|
|
5708
|
+
"integer",
|
|
5709
|
+
"bigint",
|
|
5710
|
+
"smallint",
|
|
5711
|
+
"numeric",
|
|
5712
|
+
"decimal",
|
|
5713
|
+
"float4",
|
|
5714
|
+
"float8",
|
|
5715
|
+
"real",
|
|
5716
|
+
"text",
|
|
5717
|
+
"varchar",
|
|
5718
|
+
"bpchar",
|
|
5719
|
+
"uuid",
|
|
5720
|
+
"date",
|
|
5721
|
+
"timestamp",
|
|
5722
|
+
"timestamptz",
|
|
5723
|
+
"time",
|
|
5724
|
+
"timetz",
|
|
5725
|
+
"interval",
|
|
5726
|
+
"json",
|
|
5727
|
+
"jsonb",
|
|
5728
|
+
"bytea"
|
|
5729
|
+
]);
|
|
5730
|
+
var sqlValues = /* @__PURE__ */ new Set([
|
|
5731
|
+
"SVFOP_CURRENT_DATE",
|
|
5732
|
+
"SVFOP_CURRENT_TIME",
|
|
5733
|
+
"SVFOP_CURRENT_TIME_N",
|
|
5734
|
+
"SVFOP_CURRENT_TIMESTAMP",
|
|
5735
|
+
"SVFOP_CURRENT_TIMESTAMP_N",
|
|
5736
|
+
"SVFOP_LOCALTIME",
|
|
5737
|
+
"SVFOP_LOCALTIME_N",
|
|
5738
|
+
"SVFOP_LOCALTIMESTAMP",
|
|
5739
|
+
"SVFOP_LOCALTIMESTAMP_N",
|
|
5740
|
+
"SVFOP_CURRENT_ROLE",
|
|
5741
|
+
"SVFOP_CURRENT_USER",
|
|
5742
|
+
"SVFOP_USER",
|
|
5743
|
+
"SVFOP_SESSION_USER",
|
|
5744
|
+
"SVFOP_CURRENT_CATALOG",
|
|
5745
|
+
"SVFOP_CURRENT_SCHEMA"
|
|
5746
|
+
]);
|
|
5747
|
+
async function validateManagedViewQueries({ canonical }) {
|
|
5748
|
+
for (const definition of [canonical, ...canonical.imports ?? []]) {
|
|
5749
|
+
for (const view of definition.views ?? []) {
|
|
5750
|
+
const fail4 = (message) => {
|
|
5751
|
+
throw new OrmDefinitionError(`managed view '${definition.namespace}/${view.view}': ${message}`);
|
|
5752
|
+
};
|
|
5753
|
+
parser2 ??= new PgParser({ version: 17 });
|
|
5754
|
+
const parsed = await parser2.parse(view.query);
|
|
5755
|
+
if (parsed.error || !parsed.tree) fail4("invalid SELECT query");
|
|
5756
|
+
const statements = list(obj(parsed.tree).stmts);
|
|
5757
|
+
if (statements.length !== 1 || !obj(obj(statements[0]).stmt).SelectStmt) fail4("one SELECT query is required");
|
|
5758
|
+
validateTree({ tree: parsed.tree, schema: ormSchemaName(definition.namespace), enums: definition.enums.map((entry) => entry.name), fail: fail4 });
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5762
|
+
function pgCatalogName(parts) {
|
|
5763
|
+
if (parts.length === 1) return parts[0];
|
|
5764
|
+
if (parts.length === 2 && parts[0] === "pg_catalog") return parts[1];
|
|
5765
|
+
return void 0;
|
|
5766
|
+
}
|
|
5767
|
+
function reviewedFunctionName({ parts, format }) {
|
|
5768
|
+
return format === "COERCE_SQL_SYNTAX" && parts.length === 2 && parts[0] === "pg_catalog" && parts[1] === "position" ? "strpos" : pgCatalogName(parts);
|
|
5769
|
+
}
|
|
5770
|
+
function validateTree({ tree, ...scope }) {
|
|
5771
|
+
const pending = [tree];
|
|
5772
|
+
while (pending.length) {
|
|
5773
|
+
const value = pending.pop();
|
|
5774
|
+
if (Array.isArray(value)) {
|
|
5775
|
+
pending.push(...value);
|
|
5776
|
+
continue;
|
|
5777
|
+
}
|
|
5778
|
+
if (!value || typeof value !== "object") continue;
|
|
5779
|
+
const node = obj(value);
|
|
5780
|
+
for (const [kind, child] of Object.entries(node)) {
|
|
5781
|
+
validateNode({ kind, child, scope });
|
|
5782
|
+
pending.push(child);
|
|
5783
|
+
}
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5786
|
+
function validateNode({ kind, child, scope }) {
|
|
5787
|
+
const { fail: fail4 } = scope;
|
|
5788
|
+
if (kind.endsWith("Stmt") && kind !== "SelectStmt") fail4("only SELECT statements are permitted");
|
|
5789
|
+
if (kind === "FuncCall") {
|
|
5790
|
+
const call = obj(child);
|
|
5791
|
+
const parts = strings(call.funcname);
|
|
5792
|
+
const name2 = reviewedFunctionName({ parts, format: call.funcformat });
|
|
5793
|
+
if (!name2 || !functions.has(name2)) fail4(`only explicitly allowlisted pg_catalog functions are permitted (found '${parts.join(".")}')`);
|
|
5794
|
+
}
|
|
5795
|
+
if (kind === "typeName" || kind === "TypeName") validateType({ node: obj(child), ...scope });
|
|
5796
|
+
if (kind === "A_Expr" && pgCatalogName(strings(obj(child).name)) === void 0) fail4("only pg_catalog operators are permitted");
|
|
5797
|
+
if (kind === "SortBy" && list(obj(child).useOp).length && pgCatalogName(strings(obj(child).useOp)) === void 0) fail4("only pg_catalog sort operators are permitted");
|
|
5798
|
+
if (kind === "SQLValueFunction" && !sqlValues.has(String(obj(child).op))) fail4("unsupported SQL value expression");
|
|
5799
|
+
if (kind === "ParamRef") fail4("view queries cannot contain parameters");
|
|
5800
|
+
if (kind === "A_Indirection" && list(obj(child).indirection).some((entry) => !obj(entry).A_Indices)) {
|
|
5801
|
+
fail4("composite field notation is not permitted in managed views");
|
|
5802
|
+
}
|
|
5803
|
+
}
|
|
5804
|
+
function validateType({ node, schema, enums, fail: fail4 }) {
|
|
5805
|
+
const parts = strings(node.names);
|
|
5806
|
+
const builtin = pgCatalogName(parts);
|
|
5807
|
+
const managedEnum = parts.length === 1 && enums.includes(parts[0]) || parts.length === 2 && parts[0] === schema && enums.includes(parts[1]);
|
|
5808
|
+
if (node.setof || node.pct_type || !managedEnum && (!builtin || !scalarTypes.has(builtin))) {
|
|
5809
|
+
fail4(`casts may only use supported pg_catalog scalar types or declared enums (found '${parts.join(".")}')`);
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5617
5812
|
|
|
5618
5813
|
// src/sql/operations.ts
|
|
5619
5814
|
function isSqlOperationDocument(path, contents) {
|
|
@@ -5629,7 +5824,13 @@ function isSqlOperationDocument(path, contents) {
|
|
|
5629
5824
|
async function compileOperationDocuments(input) {
|
|
5630
5825
|
const sqlDocuments = input.documents.filter((d) => isSqlOperationDocument(d.path, d.contents));
|
|
5631
5826
|
const result = compileNamespace({ ...input, documents: input.documents.filter((d) => !isSqlOperationDocument(d.path, d.contents)) });
|
|
5632
|
-
if (!result.ok
|
|
5827
|
+
if (!result.ok) return result;
|
|
5828
|
+
const viewDiagnostics = await managedViewDiagnostics(input.canonical);
|
|
5829
|
+
if (viewDiagnostics.length) return { ok: false, diagnostics: [...result.diagnostics, ...viewDiagnostics] };
|
|
5830
|
+
if (!sqlDocuments.length) return result;
|
|
5831
|
+
return compileSqlDocuments({ input, result, sqlDocuments });
|
|
5832
|
+
}
|
|
5833
|
+
async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
5633
5834
|
for (const document of sqlDocuments) {
|
|
5634
5835
|
try {
|
|
5635
5836
|
const definition = defineOperation(JSON.parse(document.contents));
|
|
@@ -5723,6 +5924,18 @@ async function compileOperationDocuments(input) {
|
|
|
5723
5924
|
result.manifest.opsHash = result.artifact.opsHash;
|
|
5724
5925
|
return { ...result, artifact: normalizeIr(result.artifact) };
|
|
5725
5926
|
}
|
|
5927
|
+
async function managedViewDiagnostics(canonical) {
|
|
5928
|
+
try {
|
|
5929
|
+
await validateManagedViewQueries({ canonical });
|
|
5930
|
+
return [];
|
|
5931
|
+
} catch (error) {
|
|
5932
|
+
return [{
|
|
5933
|
+
severity: "error",
|
|
5934
|
+
code: "invalid-view-query",
|
|
5935
|
+
message: error instanceof Error ? error.message : "invalid managed view query"
|
|
5936
|
+
}];
|
|
5937
|
+
}
|
|
5938
|
+
}
|
|
5726
5939
|
|
|
5727
5940
|
// src/sql/codegen.ts
|
|
5728
5941
|
var typeOf = (type) => ["integer", "double"].includes(type) ? "number" : type === "boolean" ? "boolean" : "string";
|
|
@@ -7818,7 +8031,7 @@ function snapshotTableNames(snapshot, schema) {
|
|
|
7818
8031
|
// src/engine/materialized-refresh.ts
|
|
7819
8032
|
function materializedRefreshFunctions(canonical) {
|
|
7820
8033
|
const schema = ormSchemaName(canonical.namespace);
|
|
7821
|
-
const
|
|
8034
|
+
const functions2 = /* @__PURE__ */ new Map();
|
|
7822
8035
|
for (const view of canonical.views ?? []) {
|
|
7823
8036
|
const materialized = view.resolved?.materialized;
|
|
7824
8037
|
if (!materialized) continue;
|
|
@@ -7827,7 +8040,7 @@ function materializedRefreshFunctions(canonical) {
|
|
|
7827
8040
|
if (!table || pk.length === 0) {
|
|
7828
8041
|
throw new OrmDefinitionError(`materialized resolved table '${view.view}' needs its table and primary key`);
|
|
7829
8042
|
}
|
|
7830
|
-
|
|
8043
|
+
functions2.set(view.view, refreshFunctionSql({
|
|
7831
8044
|
schema,
|
|
7832
8045
|
name: view.view,
|
|
7833
8046
|
table: materialized.table,
|
|
@@ -7836,7 +8049,7 @@ function materializedRefreshFunctions(canonical) {
|
|
|
7836
8049
|
pk
|
|
7837
8050
|
}));
|
|
7838
8051
|
}
|
|
7839
|
-
return
|
|
8052
|
+
return functions2;
|
|
7840
8053
|
}
|
|
7841
8054
|
function refreshFunctionSql({ schema, name: name2, table, compute, columns, pk }) {
|
|
7842
8055
|
const fn = `${quoteIdentifier(schema)}.${quoteIdentifier(materializedRefreshFunctionName(name2))}`;
|
|
@@ -8480,6 +8693,7 @@ async function planWithViewRecreation({
|
|
|
8480
8693
|
previousSnapshot,
|
|
8481
8694
|
viewRecreation
|
|
8482
8695
|
}) {
|
|
8696
|
+
await validateManagedViewQueries({ canonical });
|
|
8483
8697
|
const schema = ormSchemaName(canonical.namespace);
|
|
8484
8698
|
const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(canonical.namespace);
|
|
8485
8699
|
assertValidViewDependencies(canonical);
|
|
@@ -9770,6 +9984,9 @@ function generateSdl(canonical, options = {}) {
|
|
|
9770
9984
|
blocks.push(
|
|
9771
9985
|
'"""Gates an operation behind a platform capability or custom role \u2014 checked by the data plane before execution."""\ndirective @requires(capability: String, role: String) on QUERY | MUTATION'
|
|
9772
9986
|
);
|
|
9987
|
+
if (!options.omitReadBudgetDirective) blocks.push(
|
|
9988
|
+
'"""Sets a document-authored ceiling of 1\u201350001 rows for one flat select root. Runtime variables cannot raise it."""\ndirective @readBudget(maxRows: Int!) on QUERY'
|
|
9989
|
+
);
|
|
9773
9990
|
blocks.push("directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT");
|
|
9774
9991
|
blocks.push(
|
|
9775
9992
|
'"""Abort the entire mutation unless this root affects exactly the document-authored number of rows. Mutation roots only; cannot be conditional."""\ndirective @assertAffectedRows(exactly: Int!) on FIELD'
|
|
@@ -11974,7 +12191,7 @@ function emitSyncedTable(table) {
|
|
|
11974
12191
|
lines.push(" },", " {");
|
|
11975
12192
|
lines.push(` source: ${quote(table.source ?? "")},`);
|
|
11976
12193
|
if (table.description !== void 0) lines.push(` description: ${quote(table.description)},`);
|
|
11977
|
-
if (singlePrimaryKey === void 0 && table.primaryKey
|
|
12194
|
+
if (singlePrimaryKey === void 0 && table.primaryKey !== void 0) {
|
|
11978
12195
|
lines.push(` primaryKey: ${jsonLiteral(table.primaryKey, 4)},`);
|
|
11979
12196
|
}
|
|
11980
12197
|
if (table.links.length > 0) {
|