@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.cjs
CHANGED
|
@@ -59,18 +59,22 @@ var init_rehearsal_poll = __esm({
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
// src/constants.ts
|
|
62
|
-
var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
|
|
62
|
+
var NAMESPACE_PATTERN = /^(?!(?:events|native_sources)$)[a-z][a-z0-9_]{0,40}$/;
|
|
63
63
|
var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
64
64
|
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
65
65
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
66
66
|
var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
|
|
67
|
+
var MAX_PERSISTED_READ_ROWS = 50001;
|
|
67
68
|
var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
68
69
|
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
|
|
69
70
|
var SYNCED_SOURCE_PATTERN = QUALIFIED_TABLE_PATTERN;
|
|
70
71
|
var ORM_SCHEMA_PREFIX = "orm_";
|
|
71
72
|
var CONSUMER_ROLE_SUFFIX = "_consumer";
|
|
72
|
-
var ormSchemaName = (namespace) =>
|
|
73
|
-
|
|
73
|
+
var ormSchemaName = (namespace) => {
|
|
74
|
+
if (!NAMESPACE_PATTERN.test(namespace)) throw new Error(`invalid or reserved ORM namespace '${namespace}'`);
|
|
75
|
+
return `${ORM_SCHEMA_PREFIX}${namespace}`;
|
|
76
|
+
};
|
|
77
|
+
var consumerRoleName = (namespace) => `${ormSchemaName(namespace)}${CONSUMER_ROLE_SUFFIX}`;
|
|
74
78
|
var DEFINITION_FILE_EXTENSIONS = [".yml", ".yaml"];
|
|
75
79
|
var NAMESPACE_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["namespace.yaml", "namespace.yml"]);
|
|
76
80
|
var HASH_DOMAIN = "sequence-orm-definitions\0";
|
|
@@ -538,6 +542,7 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
538
542
|
function validateSource({ scope, source, table }) {
|
|
539
543
|
const { fail: fail4 } = scope;
|
|
540
544
|
if (source.kind === "etl" && table.source === void 0) fail4(`etl source '${source.name}' must reference a synced table`);
|
|
545
|
+
if (source.kind === "etl" && table.primaryKey?.length === 0) fail4(`etl source '${source.name}' cannot use an unkeyed synced table for resolved row identity`);
|
|
541
546
|
if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
|
|
542
547
|
fail4(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
|
|
543
548
|
}
|
|
@@ -986,7 +991,7 @@ var canonicalTableSchema = zod.z.object({
|
|
|
986
991
|
/** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
|
|
987
992
|
source: zod.z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
|
|
988
993
|
columns: zod.z.array(canonicalColumnSchema).default([]),
|
|
989
|
-
primaryKey: zod.z.array(columnIdentifier).
|
|
994
|
+
primaryKey: zod.z.array(columnIdentifier).max(8).optional(),
|
|
990
995
|
links: zod.z.array(canonicalLinkSchema).default([]),
|
|
991
996
|
indexes: zod.z.array(canonicalIndexSchema).default([]),
|
|
992
997
|
/** Omitted by old definitions, preserving their existing content hashes. */
|
|
@@ -1022,6 +1027,29 @@ var canonicalTableSchema = zod.z.object({
|
|
|
1022
1027
|
* data plane. Omitted otherwise.
|
|
1023
1028
|
*/
|
|
1024
1029
|
materializes: identifier2.optional()
|
|
1030
|
+
}).superRefine((table, ctx) => {
|
|
1031
|
+
if (table.primaryKey?.length !== 0) return;
|
|
1032
|
+
const fail4 = (message, path = ["primaryKey"]) => ctx.addIssue({ code: "custom", path, message: `unkeyed table '${table.table}' ${message}` });
|
|
1033
|
+
if (table.source === void 0 || table.columns.length === 0) {
|
|
1034
|
+
fail4("requires a typed synced source");
|
|
1035
|
+
}
|
|
1036
|
+
if ([table.policies.write, table.policies.insert, table.policies.update].some((policy) => policy !== void 0)) {
|
|
1037
|
+
fail4("is read-only and cannot declare write policies", ["policies"]);
|
|
1038
|
+
}
|
|
1039
|
+
const ownedCollections = [table.indexes, table.actions, table.checks, table.foreignKeys, table.cowMutations];
|
|
1040
|
+
const ownedMetadata = [table.cellMetadata, table.internal, table.materializes, table.renamedFrom];
|
|
1041
|
+
if (ownedCollections.some((items) => items !== void 0 && items.length > 0) || ownedMetadata.some((value) => value !== void 0)) {
|
|
1042
|
+
fail4("cannot declare owned storage, constraints or identity operations");
|
|
1043
|
+
}
|
|
1044
|
+
for (const [index3, column] of table.columns.entries()) {
|
|
1045
|
+
const ownedColumnMetadata = [column.default, column.defaultFn, column.onUpdateFn, column.renamedFrom, column.cast, column.cell];
|
|
1046
|
+
if (column.unique || ownedColumnMetadata.some((value) => value !== void 0)) {
|
|
1047
|
+
fail4("cannot declare column uniqueness, mutation defaults or migration metadata", ["columns", index3]);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
for (const [index3, link] of table.links.entries()) {
|
|
1051
|
+
if (link.enforce || link.kind === "toMany") fail4("cannot provide an identity for this link", ["links", index3]);
|
|
1052
|
+
}
|
|
1025
1053
|
});
|
|
1026
1054
|
var MAX_VIEW_QUERY = 1e5;
|
|
1027
1055
|
var MAX_RESOLVED_VIEW_QUERY = 5e6;
|
|
@@ -1078,6 +1106,26 @@ var canonicalViewSchema = zod.z.object({
|
|
|
1078
1106
|
}
|
|
1079
1107
|
}
|
|
1080
1108
|
});
|
|
1109
|
+
function validateUnkeyedLinkIdentities({ definition, ctx }) {
|
|
1110
|
+
const tables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1111
|
+
const relations = [
|
|
1112
|
+
...definition.tables.map((table, index3) => ({ kind: "tables", index: index3, links: table.links })),
|
|
1113
|
+
...(definition.views ?? []).map((view, index3) => ({ kind: "views", index: index3, links: view.links }))
|
|
1114
|
+
];
|
|
1115
|
+
for (const relation of relations) {
|
|
1116
|
+
for (const [linkIndex, link] of relation.links.entries()) {
|
|
1117
|
+
if (link.kind !== "toOne" || link.namespace !== void 0 && link.namespace !== definition.namespace) continue;
|
|
1118
|
+
const target = link.target === void 0 ? void 0 : tables.get(link.target);
|
|
1119
|
+
if (target?.source !== void 0 && target.primaryKey?.length === 0) {
|
|
1120
|
+
ctx.addIssue({
|
|
1121
|
+
code: "custom",
|
|
1122
|
+
path: [relation.kind, relation.index, "links", linkIndex],
|
|
1123
|
+
message: `link '${link.name}' cannot use unkeyed table '${target.table}' as a to-one identity`
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1081
1129
|
var CELL_PART_TYPES = {
|
|
1082
1130
|
at: "timestamptz",
|
|
1083
1131
|
by: "text",
|
|
@@ -1141,7 +1189,7 @@ var exportGateSchema = zod.z.union([zod.z.literal(true), zod.z.object({ capabili
|
|
|
1141
1189
|
var relationExportSchema = zod.z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
|
|
1142
1190
|
var localDefinitionSchema = zod.z.object({
|
|
1143
1191
|
definition_format: zod.z.literal(CANONICAL_FORMAT),
|
|
1144
|
-
namespace: identifier2.max(41),
|
|
1192
|
+
namespace: identifier2.max(41).regex(NAMESPACE_PATTERN, "invalid or reserved ORM namespace"),
|
|
1145
1193
|
description: zod.z.string().max(2e3).optional(),
|
|
1146
1194
|
enums: zod.z.array(canonicalEnumSchema).default([]),
|
|
1147
1195
|
tables: zod.z.array(canonicalTableSchema).min(0).max(500),
|
|
@@ -1175,6 +1223,7 @@ var localDefinitionSchema = zod.z.object({
|
|
|
1175
1223
|
}
|
|
1176
1224
|
}
|
|
1177
1225
|
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1226
|
+
validateUnkeyedLinkIdentities({ definition, ctx });
|
|
1178
1227
|
const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
|
|
1179
1228
|
for (const name2 of Object.keys(definition.exports ?? {})) {
|
|
1180
1229
|
if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
|
|
@@ -1497,7 +1546,7 @@ var limitIrSchema = zod.z.object({
|
|
|
1497
1546
|
variable: zod.z.string().optional(),
|
|
1498
1547
|
value: zod.z.number().int().positive().optional(),
|
|
1499
1548
|
default: zod.z.number().int().positive(),
|
|
1500
|
-
max: zod.z.number().int().positive()
|
|
1549
|
+
max: zod.z.number().int().positive().max(MAX_PERSISTED_READ_ROWS)
|
|
1501
1550
|
});
|
|
1502
1551
|
var selectionIrSchema = zod.z.lazy(
|
|
1503
1552
|
() => zod.z.object({
|
|
@@ -3121,6 +3170,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
3121
3170
|
const platformMeta = usesPlatformUsers ? platformUserMeta() : void 0;
|
|
3122
3171
|
if (platformMeta) tables[PLATFORM_USER_TABLE] = platformMeta;
|
|
3123
3172
|
const syncedTableNames = new Set(canonical.tables.filter((t2) => t2.source !== void 0).map((t2) => t2.table));
|
|
3173
|
+
const unkeyedSyncedTableNames = new Set(relations.filter((table) => table.source !== void 0 && table.primaryKey?.length === 0).map((table) => table.table));
|
|
3124
3174
|
const viewDependencies = new Map((canonical.views ?? []).map((view) => [view.view, view.dependencies]));
|
|
3125
3175
|
const claimsGatedMemo = /* @__PURE__ */ new Map();
|
|
3126
3176
|
const relationIsClaimsGated = (name2) => {
|
|
@@ -3134,6 +3184,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
3134
3184
|
};
|
|
3135
3185
|
for (const table of relations) {
|
|
3136
3186
|
const isView = viewNames.has(table.table);
|
|
3187
|
+
const readOnly = isView || unkeyedSyncedTableNames.has(table.table);
|
|
3137
3188
|
const owner = owners.get(table.table);
|
|
3138
3189
|
const entries = resolveColumnEntries(table, enums, diagnostics);
|
|
3139
3190
|
columnEntries.set(table.table, entries);
|
|
@@ -3181,13 +3232,13 @@ function buildCompileContext(rawCanonical) {
|
|
|
3181
3232
|
// are claims-gated — see tableMetaIrSchema.claimsGated.
|
|
3182
3233
|
claimsGated: relationIsClaimsGated(table.table),
|
|
3183
3234
|
columns: entries.map(({ deprecated: _deprecated, onUpdateFn: _onUpdateFn, ...column }) => column),
|
|
3184
|
-
primaryKey: isView ? primaryKey2 : primaryKey2.length > 0 ? primaryKey2 : ["id"],
|
|
3235
|
+
primaryKey: isView || unkeyedSyncedTableNames.has(table.table) ? primaryKey2 : primaryKey2.length > 0 ? primaryKey2 : ["id"],
|
|
3185
3236
|
links: [],
|
|
3186
3237
|
filterable: data.map((c) => c.name),
|
|
3187
3238
|
orderable: data.filter((c) => c.type !== "jsonb").map((c) => c.name),
|
|
3188
|
-
insertable: resolved ? resolvedWritable(data) :
|
|
3189
|
-
settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey2.includes(name2)) :
|
|
3190
|
-
appendable:
|
|
3239
|
+
insertable: resolved ? resolvedWritable(data) : readOnly ? [] : active.map((c) => c.name),
|
|
3240
|
+
settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey2.includes(name2)) : readOnly ? [] : active.filter((c) => !c.onUpdateFn).map((c) => c.name),
|
|
3241
|
+
appendable: readOnly ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
|
|
3191
3242
|
...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
|
|
3192
3243
|
...cells ? { cells } : {},
|
|
3193
3244
|
...resolved ? { resolved: resolvedMeta({ resolved, pk: primaryKey2[0] }) } : {},
|
|
@@ -3211,6 +3262,16 @@ function buildCompileContext(rawCanonical) {
|
|
|
3211
3262
|
});
|
|
3212
3263
|
continue;
|
|
3213
3264
|
}
|
|
3265
|
+
const identityTable = link.kind === "toOne" ? targetName : link.kind === "toMany" ? table.table : void 0;
|
|
3266
|
+
if (identityTable !== void 0 && unkeyedSyncedTableNames.has(identityTable)) {
|
|
3267
|
+
diagnostics.push({
|
|
3268
|
+
severity: "error",
|
|
3269
|
+
code: "unkeyed-link-identity",
|
|
3270
|
+
table: table.table,
|
|
3271
|
+
message: `link '${link.name}' cannot use unkeyed table '${identityTable}' as a row identity`
|
|
3272
|
+
});
|
|
3273
|
+
continue;
|
|
3274
|
+
}
|
|
3214
3275
|
const targetEntries = targetName === PLATFORM_USER_TABLE ? target.columns.map((c) => ({ ...c, deprecated: false, onUpdateFn: false })) : columnEntries.get(targetName);
|
|
3215
3276
|
const onEntries = link.kind === "toMany" ? targetEntries : entries;
|
|
3216
3277
|
const referencesEntries = link.kind === "toMany" ? entries : targetEntries;
|
|
@@ -3276,7 +3337,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
3276
3337
|
compilePolicyFor("write", writePolicies);
|
|
3277
3338
|
compilePolicyFor("insert", insertPolicies);
|
|
3278
3339
|
compilePolicyFor("update", updatePolicies);
|
|
3279
|
-
if (isView && table.resolved === void 0) {
|
|
3340
|
+
if (isView && table.resolved === void 0 || unkeyedSyncedTableNames.has(table.table)) {
|
|
3280
3341
|
writePolicies.set(table.table, CONST_FALSE);
|
|
3281
3342
|
insertPolicies.set(table.table, CONST_FALSE);
|
|
3282
3343
|
updatePolicies.set(table.table, CONST_FALSE);
|
|
@@ -3351,7 +3412,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
3351
3412
|
const queryRoots = /* @__PURE__ */ new Map();
|
|
3352
3413
|
const mutationRoots = /* @__PURE__ */ new Map();
|
|
3353
3414
|
for (const table of relations) {
|
|
3354
|
-
if (table.internal === true) continue;
|
|
3415
|
+
if (table.internal === true || unkeyedSyncedTableNames.has(table.table)) continue;
|
|
3355
3416
|
const roots = queryRootNames(table.table);
|
|
3356
3417
|
queryRoots.set(roots.select, { kind: "select", table: table.table });
|
|
3357
3418
|
if ((tables[table.table]?.primaryKey.length ?? 0) > 0) {
|
|
@@ -3731,8 +3792,10 @@ var OperationLowerer = class {
|
|
|
3731
3792
|
constructor(ctx, restricted) {
|
|
3732
3793
|
this.ctx = ctx;
|
|
3733
3794
|
this.variables = new Map(restricted.variables.map((v) => [v.name, v]));
|
|
3795
|
+
this.maxRows = restricted.readBudget ?? MAX_LIMIT;
|
|
3734
3796
|
}
|
|
3735
3797
|
variables;
|
|
3798
|
+
maxRows;
|
|
3736
3799
|
meta(table) {
|
|
3737
3800
|
return this.ctx.tables[table];
|
|
3738
3801
|
}
|
|
@@ -3936,13 +3999,13 @@ var OperationLowerer = class {
|
|
|
3936
3999
|
});
|
|
3937
4000
|
}
|
|
3938
4001
|
lowerLimit(node) {
|
|
3939
|
-
const base2 = { default: DEFAULT_LIMIT, max:
|
|
4002
|
+
const base2 = { default: DEFAULT_LIMIT, max: this.maxRows };
|
|
3940
4003
|
if (!node) return base2;
|
|
3941
4004
|
if (node.kind === graphql.Kind.VARIABLE) return { ...base2, variable: node.name.value };
|
|
3942
4005
|
if (node.kind === graphql.Kind.INT) {
|
|
3943
4006
|
const value = Number(node.value);
|
|
3944
4007
|
if (value < 1) fail2("limit must be a positive integer", "invalid-limit");
|
|
3945
|
-
return { ...base2, value: Math.min(value,
|
|
4008
|
+
return { ...base2, value: Math.min(value, this.maxRows) };
|
|
3946
4009
|
}
|
|
3947
4010
|
fail2("limit must be an integer literal or a variable", "invalid-limit");
|
|
3948
4011
|
}
|
|
@@ -4741,7 +4804,13 @@ var OperationLowerer = class {
|
|
|
4741
4804
|
};
|
|
4742
4805
|
function lowerOperation({ restricted, ctx }) {
|
|
4743
4806
|
const lowerer = new OperationLowerer(ctx, restricted);
|
|
4744
|
-
|
|
4807
|
+
const roots = lowerer.lowerRoots(restricted.definition, restricted.kind);
|
|
4808
|
+
if (restricted.readBudget === void 0) return roots;
|
|
4809
|
+
const root = roots[0];
|
|
4810
|
+
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) {
|
|
4811
|
+
fail2("@readBudget requires one flat select root without related selections, aggregates, cells, or prefetch", "invalid-read-budget");
|
|
4812
|
+
}
|
|
4813
|
+
return roots;
|
|
4745
4814
|
}
|
|
4746
4815
|
function isCellColumn(meta, name2) {
|
|
4747
4816
|
if (!meta.cells) return false;
|
|
@@ -4800,6 +4869,19 @@ function parseDocuments({
|
|
|
4800
4869
|
}
|
|
4801
4870
|
return { documents: parsed, diagnostics };
|
|
4802
4871
|
}
|
|
4872
|
+
function parseReadBudget(operation) {
|
|
4873
|
+
const directives = operation.directives?.filter((directive) => directive.name.value === "readBudget") ?? [];
|
|
4874
|
+
if (directives.length === 0) return void 0;
|
|
4875
|
+
const value = directives[0]?.arguments?.find((argument) => argument.name.value === "maxRows")?.value;
|
|
4876
|
+
if (operation.operation !== "query" || directives.length !== 1 || value?.kind !== graphql.Kind.INT) {
|
|
4877
|
+
throw new DocumentCompileError("@readBudget requires a literal maxRows on a query", "invalid-read-budget");
|
|
4878
|
+
}
|
|
4879
|
+
const maxRows = Number(value.value);
|
|
4880
|
+
if (!Number.isSafeInteger(maxRows) || maxRows < 1 || maxRows > MAX_PERSISTED_READ_ROWS) {
|
|
4881
|
+
throw new DocumentCompileError(`@readBudget(maxRows:) must be between 1 and ${MAX_PERSISTED_READ_ROWS}`, "invalid-read-budget");
|
|
4882
|
+
}
|
|
4883
|
+
return maxRows;
|
|
4884
|
+
}
|
|
4803
4885
|
function unwrapType(node, variable) {
|
|
4804
4886
|
let nullable = true;
|
|
4805
4887
|
let list2 = false;
|
|
@@ -4960,6 +5042,7 @@ function restrictOperation({
|
|
|
4960
5042
|
name: name2,
|
|
4961
5043
|
kind: operation.operation === "mutation" ? "mutation" : "query",
|
|
4962
5044
|
requires: parseRequires(operation),
|
|
5045
|
+
readBudget: parseReadBudget(operation),
|
|
4963
5046
|
variables,
|
|
4964
5047
|
definition: operation
|
|
4965
5048
|
};
|
|
@@ -5617,6 +5700,118 @@ function bindNamedParameters({ sql: sql4, names }) {
|
|
|
5617
5700
|
}
|
|
5618
5701
|
return result;
|
|
5619
5702
|
}
|
|
5703
|
+
var parser2;
|
|
5704
|
+
var functions = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
|
|
5705
|
+
var scalarTypes = /* @__PURE__ */ new Set([
|
|
5706
|
+
"bool",
|
|
5707
|
+
"boolean",
|
|
5708
|
+
"int2",
|
|
5709
|
+
"int4",
|
|
5710
|
+
"int8",
|
|
5711
|
+
"integer",
|
|
5712
|
+
"bigint",
|
|
5713
|
+
"smallint",
|
|
5714
|
+
"numeric",
|
|
5715
|
+
"decimal",
|
|
5716
|
+
"float4",
|
|
5717
|
+
"float8",
|
|
5718
|
+
"real",
|
|
5719
|
+
"text",
|
|
5720
|
+
"varchar",
|
|
5721
|
+
"bpchar",
|
|
5722
|
+
"uuid",
|
|
5723
|
+
"date",
|
|
5724
|
+
"timestamp",
|
|
5725
|
+
"timestamptz",
|
|
5726
|
+
"time",
|
|
5727
|
+
"timetz",
|
|
5728
|
+
"interval",
|
|
5729
|
+
"json",
|
|
5730
|
+
"jsonb",
|
|
5731
|
+
"bytea"
|
|
5732
|
+
]);
|
|
5733
|
+
var sqlValues = /* @__PURE__ */ new Set([
|
|
5734
|
+
"SVFOP_CURRENT_DATE",
|
|
5735
|
+
"SVFOP_CURRENT_TIME",
|
|
5736
|
+
"SVFOP_CURRENT_TIME_N",
|
|
5737
|
+
"SVFOP_CURRENT_TIMESTAMP",
|
|
5738
|
+
"SVFOP_CURRENT_TIMESTAMP_N",
|
|
5739
|
+
"SVFOP_LOCALTIME",
|
|
5740
|
+
"SVFOP_LOCALTIME_N",
|
|
5741
|
+
"SVFOP_LOCALTIMESTAMP",
|
|
5742
|
+
"SVFOP_LOCALTIMESTAMP_N",
|
|
5743
|
+
"SVFOP_CURRENT_ROLE",
|
|
5744
|
+
"SVFOP_CURRENT_USER",
|
|
5745
|
+
"SVFOP_USER",
|
|
5746
|
+
"SVFOP_SESSION_USER",
|
|
5747
|
+
"SVFOP_CURRENT_CATALOG",
|
|
5748
|
+
"SVFOP_CURRENT_SCHEMA"
|
|
5749
|
+
]);
|
|
5750
|
+
async function validateManagedViewQueries({ canonical }) {
|
|
5751
|
+
for (const definition of [canonical, ...canonical.imports ?? []]) {
|
|
5752
|
+
for (const view of definition.views ?? []) {
|
|
5753
|
+
const fail4 = (message) => {
|
|
5754
|
+
throw new OrmDefinitionError(`managed view '${definition.namespace}/${view.view}': ${message}`);
|
|
5755
|
+
};
|
|
5756
|
+
parser2 ??= new pgParser.PgParser({ version: 17 });
|
|
5757
|
+
const parsed = await parser2.parse(view.query);
|
|
5758
|
+
if (parsed.error || !parsed.tree) fail4("invalid SELECT query");
|
|
5759
|
+
const statements = list(obj(parsed.tree).stmts);
|
|
5760
|
+
if (statements.length !== 1 || !obj(obj(statements[0]).stmt).SelectStmt) fail4("one SELECT query is required");
|
|
5761
|
+
validateTree({ tree: parsed.tree, schema: ormSchemaName(definition.namespace), enums: definition.enums.map((entry) => entry.name), fail: fail4 });
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5764
|
+
}
|
|
5765
|
+
function pgCatalogName(parts) {
|
|
5766
|
+
if (parts.length === 1) return parts[0];
|
|
5767
|
+
if (parts.length === 2 && parts[0] === "pg_catalog") return parts[1];
|
|
5768
|
+
return void 0;
|
|
5769
|
+
}
|
|
5770
|
+
function reviewedFunctionName({ parts, format }) {
|
|
5771
|
+
return format === "COERCE_SQL_SYNTAX" && parts.length === 2 && parts[0] === "pg_catalog" && parts[1] === "position" ? "strpos" : pgCatalogName(parts);
|
|
5772
|
+
}
|
|
5773
|
+
function validateTree({ tree, ...scope }) {
|
|
5774
|
+
const pending = [tree];
|
|
5775
|
+
while (pending.length) {
|
|
5776
|
+
const value = pending.pop();
|
|
5777
|
+
if (Array.isArray(value)) {
|
|
5778
|
+
pending.push(...value);
|
|
5779
|
+
continue;
|
|
5780
|
+
}
|
|
5781
|
+
if (!value || typeof value !== "object") continue;
|
|
5782
|
+
const node = obj(value);
|
|
5783
|
+
for (const [kind, child] of Object.entries(node)) {
|
|
5784
|
+
validateNode({ kind, child, scope });
|
|
5785
|
+
pending.push(child);
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
}
|
|
5789
|
+
function validateNode({ kind, child, scope }) {
|
|
5790
|
+
const { fail: fail4 } = scope;
|
|
5791
|
+
if (kind.endsWith("Stmt") && kind !== "SelectStmt") fail4("only SELECT statements are permitted");
|
|
5792
|
+
if (kind === "FuncCall") {
|
|
5793
|
+
const call = obj(child);
|
|
5794
|
+
const parts = strings(call.funcname);
|
|
5795
|
+
const name2 = reviewedFunctionName({ parts, format: call.funcformat });
|
|
5796
|
+
if (!name2 || !functions.has(name2)) fail4(`only explicitly allowlisted pg_catalog functions are permitted (found '${parts.join(".")}')`);
|
|
5797
|
+
}
|
|
5798
|
+
if (kind === "typeName" || kind === "TypeName") validateType({ node: obj(child), ...scope });
|
|
5799
|
+
if (kind === "A_Expr" && pgCatalogName(strings(obj(child).name)) === void 0) fail4("only pg_catalog operators are permitted");
|
|
5800
|
+
if (kind === "SortBy" && list(obj(child).useOp).length && pgCatalogName(strings(obj(child).useOp)) === void 0) fail4("only pg_catalog sort operators are permitted");
|
|
5801
|
+
if (kind === "SQLValueFunction" && !sqlValues.has(String(obj(child).op))) fail4("unsupported SQL value expression");
|
|
5802
|
+
if (kind === "ParamRef") fail4("view queries cannot contain parameters");
|
|
5803
|
+
if (kind === "A_Indirection" && list(obj(child).indirection).some((entry) => !obj(entry).A_Indices)) {
|
|
5804
|
+
fail4("composite field notation is not permitted in managed views");
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
function validateType({ node, schema, enums, fail: fail4 }) {
|
|
5808
|
+
const parts = strings(node.names);
|
|
5809
|
+
const builtin = pgCatalogName(parts);
|
|
5810
|
+
const managedEnum = parts.length === 1 && enums.includes(parts[0]) || parts.length === 2 && parts[0] === schema && enums.includes(parts[1]);
|
|
5811
|
+
if (node.setof || node.pct_type || !managedEnum && (!builtin || !scalarTypes.has(builtin))) {
|
|
5812
|
+
fail4(`casts may only use supported pg_catalog scalar types or declared enums (found '${parts.join(".")}')`);
|
|
5813
|
+
}
|
|
5814
|
+
}
|
|
5620
5815
|
|
|
5621
5816
|
// src/sql/operations.ts
|
|
5622
5817
|
function isSqlOperationDocument(path, contents) {
|
|
@@ -5632,7 +5827,13 @@ function isSqlOperationDocument(path, contents) {
|
|
|
5632
5827
|
async function compileOperationDocuments(input) {
|
|
5633
5828
|
const sqlDocuments = input.documents.filter((d) => isSqlOperationDocument(d.path, d.contents));
|
|
5634
5829
|
const result = compileNamespace({ ...input, documents: input.documents.filter((d) => !isSqlOperationDocument(d.path, d.contents)) });
|
|
5635
|
-
if (!result.ok
|
|
5830
|
+
if (!result.ok) return result;
|
|
5831
|
+
const viewDiagnostics = await managedViewDiagnostics(input.canonical);
|
|
5832
|
+
if (viewDiagnostics.length) return { ok: false, diagnostics: [...result.diagnostics, ...viewDiagnostics] };
|
|
5833
|
+
if (!sqlDocuments.length) return result;
|
|
5834
|
+
return compileSqlDocuments({ input, result, sqlDocuments });
|
|
5835
|
+
}
|
|
5836
|
+
async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
5636
5837
|
for (const document of sqlDocuments) {
|
|
5637
5838
|
try {
|
|
5638
5839
|
const definition = defineOperation(JSON.parse(document.contents));
|
|
@@ -5726,6 +5927,18 @@ async function compileOperationDocuments(input) {
|
|
|
5726
5927
|
result.manifest.opsHash = result.artifact.opsHash;
|
|
5727
5928
|
return { ...result, artifact: normalizeIr(result.artifact) };
|
|
5728
5929
|
}
|
|
5930
|
+
async function managedViewDiagnostics(canonical) {
|
|
5931
|
+
try {
|
|
5932
|
+
await validateManagedViewQueries({ canonical });
|
|
5933
|
+
return [];
|
|
5934
|
+
} catch (error) {
|
|
5935
|
+
return [{
|
|
5936
|
+
severity: "error",
|
|
5937
|
+
code: "invalid-view-query",
|
|
5938
|
+
message: error instanceof Error ? error.message : "invalid managed view query"
|
|
5939
|
+
}];
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5729
5942
|
|
|
5730
5943
|
// src/sql/codegen.ts
|
|
5731
5944
|
var typeOf = (type) => ["integer", "double"].includes(type) ? "number" : type === "boolean" ? "boolean" : "string";
|
|
@@ -7821,7 +8034,7 @@ function snapshotTableNames(snapshot, schema) {
|
|
|
7821
8034
|
// src/engine/materialized-refresh.ts
|
|
7822
8035
|
function materializedRefreshFunctions(canonical) {
|
|
7823
8036
|
const schema = ormSchemaName(canonical.namespace);
|
|
7824
|
-
const
|
|
8037
|
+
const functions2 = /* @__PURE__ */ new Map();
|
|
7825
8038
|
for (const view of canonical.views ?? []) {
|
|
7826
8039
|
const materialized = view.resolved?.materialized;
|
|
7827
8040
|
if (!materialized) continue;
|
|
@@ -7830,7 +8043,7 @@ function materializedRefreshFunctions(canonical) {
|
|
|
7830
8043
|
if (!table || pk.length === 0) {
|
|
7831
8044
|
throw new OrmDefinitionError(`materialized resolved table '${view.view}' needs its table and primary key`);
|
|
7832
8045
|
}
|
|
7833
|
-
|
|
8046
|
+
functions2.set(view.view, refreshFunctionSql({
|
|
7834
8047
|
schema,
|
|
7835
8048
|
name: view.view,
|
|
7836
8049
|
table: materialized.table,
|
|
@@ -7839,7 +8052,7 @@ function materializedRefreshFunctions(canonical) {
|
|
|
7839
8052
|
pk
|
|
7840
8053
|
}));
|
|
7841
8054
|
}
|
|
7842
|
-
return
|
|
8055
|
+
return functions2;
|
|
7843
8056
|
}
|
|
7844
8057
|
function refreshFunctionSql({ schema, name: name2, table, compute, columns, pk }) {
|
|
7845
8058
|
const fn = `${quoteIdentifier(schema)}.${quoteIdentifier(materializedRefreshFunctionName(name2))}`;
|
|
@@ -8483,6 +8696,7 @@ async function planWithViewRecreation({
|
|
|
8483
8696
|
previousSnapshot,
|
|
8484
8697
|
viewRecreation
|
|
8485
8698
|
}) {
|
|
8699
|
+
await validateManagedViewQueries({ canonical });
|
|
8486
8700
|
const schema = ormSchemaName(canonical.namespace);
|
|
8487
8701
|
const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(canonical.namespace);
|
|
8488
8702
|
assertValidViewDependencies(canonical);
|
|
@@ -9773,6 +9987,9 @@ function generateSdl(canonical, options = {}) {
|
|
|
9773
9987
|
blocks.push(
|
|
9774
9988
|
'"""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'
|
|
9775
9989
|
);
|
|
9990
|
+
if (!options.omitReadBudgetDirective) blocks.push(
|
|
9991
|
+
'"""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'
|
|
9992
|
+
);
|
|
9776
9993
|
blocks.push("directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT");
|
|
9777
9994
|
blocks.push(
|
|
9778
9995
|
'"""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'
|
|
@@ -11977,7 +12194,7 @@ function emitSyncedTable(table) {
|
|
|
11977
12194
|
lines.push(" },", " {");
|
|
11978
12195
|
lines.push(` source: ${quote(table.source ?? "")},`);
|
|
11979
12196
|
if (table.description !== void 0) lines.push(` description: ${quote(table.description)},`);
|
|
11980
|
-
if (singlePrimaryKey === void 0 && table.primaryKey
|
|
12197
|
+
if (singlePrimaryKey === void 0 && table.primaryKey !== void 0) {
|
|
11981
12198
|
lines.push(` primaryKey: ${jsonLiteral(table.primaryKey, 4)},`);
|
|
11982
12199
|
}
|
|
11983
12200
|
if (table.links.length > 0) {
|