@sequenceholdings/orm 2.3.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 +337 -86
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +337 -86
- 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 +493 -52
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +22 -7
- package/dist/compiler.d.ts +22 -7
- package/dist/compiler.js +493 -53
- 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 +145 -22
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.d.cts +41 -4
- package/dist/predicate-v2.d.ts +41 -4
- package/dist/predicate-v2.js +145 -22
- 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/compiler.js
CHANGED
|
@@ -6,12 +6,17 @@ import { PgParser } from '@supabase/pg-parser';
|
|
|
6
6
|
// src/compiler-gql/artifact.ts
|
|
7
7
|
|
|
8
8
|
// src/constants.ts
|
|
9
|
+
var NAMESPACE_PATTERN = /^(?!(?:events|native_sources)$)[a-z][a-z0-9_]{0,40}$/;
|
|
9
10
|
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
10
11
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
11
12
|
var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
|
|
13
|
+
var MAX_PERSISTED_READ_ROWS = 50001;
|
|
12
14
|
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
|
|
13
15
|
var ORM_SCHEMA_PREFIX = "orm_";
|
|
14
|
-
var ormSchemaName = (namespace) =>
|
|
16
|
+
var ormSchemaName = (namespace) => {
|
|
17
|
+
if (!NAMESPACE_PATTERN.test(namespace)) throw new Error(`invalid or reserved ORM namespace '${namespace}'`);
|
|
18
|
+
return `${ORM_SCHEMA_PREFIX}${namespace}`;
|
|
19
|
+
};
|
|
15
20
|
var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
|
|
16
21
|
var CELL_PART_SUFFIX = {
|
|
17
22
|
at: "__at",
|
|
@@ -387,6 +392,7 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
387
392
|
function validateSource({ scope, source, table }) {
|
|
388
393
|
const { fail: fail4 } = scope;
|
|
389
394
|
if (source.kind === "etl" && table.source === void 0) fail4(`etl source '${source.name}' must reference a synced table`);
|
|
395
|
+
if (source.kind === "etl" && table.primaryKey?.length === 0) fail4(`etl source '${source.name}' cannot use an unkeyed synced table for resolved row identity`);
|
|
390
396
|
if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
|
|
391
397
|
fail4(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
|
|
392
398
|
}
|
|
@@ -618,6 +624,163 @@ function isSingleViewQuerySql(value) {
|
|
|
618
624
|
}
|
|
619
625
|
return state === "normal" && !/\$[a-z_0-9]*\$/i.test(query);
|
|
620
626
|
}
|
|
627
|
+
var VIEW_QUERY_FUNCTION_ALLOWLIST = [
|
|
628
|
+
"abs",
|
|
629
|
+
"age",
|
|
630
|
+
"array_agg",
|
|
631
|
+
"array_append",
|
|
632
|
+
"array_cat",
|
|
633
|
+
"array_dims",
|
|
634
|
+
"array_length",
|
|
635
|
+
"array_lower",
|
|
636
|
+
"array_ndims",
|
|
637
|
+
"array_position",
|
|
638
|
+
"array_positions",
|
|
639
|
+
"array_prepend",
|
|
640
|
+
"array_remove",
|
|
641
|
+
"array_replace",
|
|
642
|
+
"array_to_string",
|
|
643
|
+
"array_trim",
|
|
644
|
+
"array_upper",
|
|
645
|
+
"ascii",
|
|
646
|
+
"avg",
|
|
647
|
+
"bit_and",
|
|
648
|
+
"bit_or",
|
|
649
|
+
"bit_xor",
|
|
650
|
+
"bool_and",
|
|
651
|
+
"bool_or",
|
|
652
|
+
"btrim",
|
|
653
|
+
"cardinality",
|
|
654
|
+
"cbrt",
|
|
655
|
+
"ceil",
|
|
656
|
+
"ceiling",
|
|
657
|
+
"char_length",
|
|
658
|
+
"character_length",
|
|
659
|
+
"chr",
|
|
660
|
+
"concat",
|
|
661
|
+
"concat_ws",
|
|
662
|
+
"count",
|
|
663
|
+
"cume_dist",
|
|
664
|
+
"date_bin",
|
|
665
|
+
"date_part",
|
|
666
|
+
"date_trunc",
|
|
667
|
+
"decode",
|
|
668
|
+
"degrees",
|
|
669
|
+
"dense_rank",
|
|
670
|
+
"encode",
|
|
671
|
+
"enum_first",
|
|
672
|
+
"enum_last",
|
|
673
|
+
"enum_range",
|
|
674
|
+
"every",
|
|
675
|
+
"exp",
|
|
676
|
+
"first_value",
|
|
677
|
+
"floor",
|
|
678
|
+
"format",
|
|
679
|
+
"grouping",
|
|
680
|
+
"initcap",
|
|
681
|
+
"json_agg",
|
|
682
|
+
"json_array_length",
|
|
683
|
+
"json_build_array",
|
|
684
|
+
"json_build_object",
|
|
685
|
+
"json_extract_path",
|
|
686
|
+
"json_extract_path_text",
|
|
687
|
+
"json_object_agg",
|
|
688
|
+
"json_object_keys",
|
|
689
|
+
"json_strip_nulls",
|
|
690
|
+
"json_typeof",
|
|
691
|
+
"jsonb_agg",
|
|
692
|
+
"jsonb_array_length",
|
|
693
|
+
"jsonb_build_array",
|
|
694
|
+
"jsonb_build_object",
|
|
695
|
+
"jsonb_extract_path",
|
|
696
|
+
"jsonb_extract_path_text",
|
|
697
|
+
"jsonb_insert",
|
|
698
|
+
"jsonb_object_agg",
|
|
699
|
+
"jsonb_object_keys",
|
|
700
|
+
"jsonb_path_exists",
|
|
701
|
+
"jsonb_path_match",
|
|
702
|
+
"jsonb_path_query",
|
|
703
|
+
"jsonb_path_query_array",
|
|
704
|
+
"jsonb_path_query_first",
|
|
705
|
+
"jsonb_pretty",
|
|
706
|
+
"jsonb_set",
|
|
707
|
+
"jsonb_set_lax",
|
|
708
|
+
"jsonb_strip_nulls",
|
|
709
|
+
"jsonb_typeof",
|
|
710
|
+
"justify_days",
|
|
711
|
+
"justify_hours",
|
|
712
|
+
"justify_interval",
|
|
713
|
+
"lag",
|
|
714
|
+
"last_value",
|
|
715
|
+
"lead",
|
|
716
|
+
"left",
|
|
717
|
+
"length",
|
|
718
|
+
"ln",
|
|
719
|
+
"log",
|
|
720
|
+
"lower",
|
|
721
|
+
"lpad",
|
|
722
|
+
"ltrim",
|
|
723
|
+
"make_date",
|
|
724
|
+
"make_interval",
|
|
725
|
+
"make_time",
|
|
726
|
+
"make_timestamp",
|
|
727
|
+
"make_timestamptz",
|
|
728
|
+
"max",
|
|
729
|
+
"md5",
|
|
730
|
+
"min",
|
|
731
|
+
"mod",
|
|
732
|
+
"nth_value",
|
|
733
|
+
"ntile",
|
|
734
|
+
"octet_length",
|
|
735
|
+
"percent_rank",
|
|
736
|
+
"pi",
|
|
737
|
+
"power",
|
|
738
|
+
"quote_ident",
|
|
739
|
+
"quote_literal",
|
|
740
|
+
"quote_nullable",
|
|
741
|
+
"radians",
|
|
742
|
+
"rank",
|
|
743
|
+
"regexp_count",
|
|
744
|
+
"regexp_instr",
|
|
745
|
+
"regexp_like",
|
|
746
|
+
"regexp_match",
|
|
747
|
+
"regexp_matches",
|
|
748
|
+
"regexp_replace",
|
|
749
|
+
"regexp_split_to_array",
|
|
750
|
+
"regexp_split_to_table",
|
|
751
|
+
"repeat",
|
|
752
|
+
"replace",
|
|
753
|
+
"reverse",
|
|
754
|
+
"right",
|
|
755
|
+
"round",
|
|
756
|
+
"row_number",
|
|
757
|
+
"rpad",
|
|
758
|
+
"rtrim",
|
|
759
|
+
"sign",
|
|
760
|
+
"split_part",
|
|
761
|
+
"sqrt",
|
|
762
|
+
"starts_with",
|
|
763
|
+
"string_agg",
|
|
764
|
+
"string_to_array",
|
|
765
|
+
"strpos",
|
|
766
|
+
"substr",
|
|
767
|
+
"substring",
|
|
768
|
+
"sum",
|
|
769
|
+
"timezone",
|
|
770
|
+
"to_ascii",
|
|
771
|
+
"to_char",
|
|
772
|
+
"to_date",
|
|
773
|
+
"to_hex",
|
|
774
|
+
"to_json",
|
|
775
|
+
"to_jsonb",
|
|
776
|
+
"to_number",
|
|
777
|
+
"to_timestamp",
|
|
778
|
+
"translate",
|
|
779
|
+
"trunc",
|
|
780
|
+
"unnest",
|
|
781
|
+
"upper",
|
|
782
|
+
"width_bucket"
|
|
783
|
+
];
|
|
621
784
|
var canonicalActionSchema = z.object({
|
|
622
785
|
name: identifier2,
|
|
623
786
|
requires: z.string().max(1e3).optional(),
|
|
@@ -678,7 +841,7 @@ var canonicalTableSchema = z.object({
|
|
|
678
841
|
/** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
|
|
679
842
|
source: z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
|
|
680
843
|
columns: z.array(canonicalColumnSchema).default([]),
|
|
681
|
-
primaryKey: z.array(columnIdentifier).
|
|
844
|
+
primaryKey: z.array(columnIdentifier).max(8).optional(),
|
|
682
845
|
links: z.array(canonicalLinkSchema).default([]),
|
|
683
846
|
indexes: z.array(canonicalIndexSchema).default([]),
|
|
684
847
|
/** Omitted by old definitions, preserving their existing content hashes. */
|
|
@@ -714,6 +877,29 @@ var canonicalTableSchema = z.object({
|
|
|
714
877
|
* data plane. Omitted otherwise.
|
|
715
878
|
*/
|
|
716
879
|
materializes: identifier2.optional()
|
|
880
|
+
}).superRefine((table, ctx) => {
|
|
881
|
+
if (table.primaryKey?.length !== 0) return;
|
|
882
|
+
const fail4 = (message, path = ["primaryKey"]) => ctx.addIssue({ code: "custom", path, message: `unkeyed table '${table.table}' ${message}` });
|
|
883
|
+
if (table.source === void 0 || table.columns.length === 0) {
|
|
884
|
+
fail4("requires a typed synced source");
|
|
885
|
+
}
|
|
886
|
+
if ([table.policies.write, table.policies.insert, table.policies.update].some((policy) => policy !== void 0)) {
|
|
887
|
+
fail4("is read-only and cannot declare write policies", ["policies"]);
|
|
888
|
+
}
|
|
889
|
+
const ownedCollections = [table.indexes, table.actions, table.checks, table.foreignKeys, table.cowMutations];
|
|
890
|
+
const ownedMetadata = [table.cellMetadata, table.internal, table.materializes, table.renamedFrom];
|
|
891
|
+
if (ownedCollections.some((items) => items !== void 0 && items.length > 0) || ownedMetadata.some((value) => value !== void 0)) {
|
|
892
|
+
fail4("cannot declare owned storage, constraints or identity operations");
|
|
893
|
+
}
|
|
894
|
+
for (const [index, column] of table.columns.entries()) {
|
|
895
|
+
const ownedColumnMetadata = [column.default, column.defaultFn, column.onUpdateFn, column.renamedFrom, column.cast, column.cell];
|
|
896
|
+
if (column.unique || ownedColumnMetadata.some((value) => value !== void 0)) {
|
|
897
|
+
fail4("cannot declare column uniqueness, mutation defaults or migration metadata", ["columns", index]);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
for (const [index, link] of table.links.entries()) {
|
|
901
|
+
if (link.enforce || link.kind === "toMany") fail4("cannot provide an identity for this link", ["links", index]);
|
|
902
|
+
}
|
|
717
903
|
});
|
|
718
904
|
var MAX_VIEW_QUERY = 1e5;
|
|
719
905
|
var MAX_RESOLVED_VIEW_QUERY = 5e6;
|
|
@@ -770,6 +956,26 @@ var canonicalViewSchema = z.object({
|
|
|
770
956
|
}
|
|
771
957
|
}
|
|
772
958
|
});
|
|
959
|
+
function validateUnkeyedLinkIdentities({ definition, ctx }) {
|
|
960
|
+
const tables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
961
|
+
const relations = [
|
|
962
|
+
...definition.tables.map((table, index) => ({ kind: "tables", index, links: table.links })),
|
|
963
|
+
...(definition.views ?? []).map((view, index) => ({ kind: "views", index, links: view.links }))
|
|
964
|
+
];
|
|
965
|
+
for (const relation of relations) {
|
|
966
|
+
for (const [linkIndex, link] of relation.links.entries()) {
|
|
967
|
+
if (link.kind !== "toOne" || link.namespace !== void 0 && link.namespace !== definition.namespace) continue;
|
|
968
|
+
const target = link.target === void 0 ? void 0 : tables.get(link.target);
|
|
969
|
+
if (target?.source !== void 0 && target.primaryKey?.length === 0) {
|
|
970
|
+
ctx.addIssue({
|
|
971
|
+
code: "custom",
|
|
972
|
+
path: [relation.kind, relation.index, "links", linkIndex],
|
|
973
|
+
message: `link '${link.name}' cannot use unkeyed table '${target.table}' as a to-one identity`
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
773
979
|
var CELL_PART_TYPES = {
|
|
774
980
|
at: "timestamptz",
|
|
775
981
|
by: "text",
|
|
@@ -833,7 +1039,7 @@ var exportGateSchema = z.union([z.literal(true), z.object({ capability: z.string
|
|
|
833
1039
|
var relationExportSchema = z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
|
|
834
1040
|
var localDefinitionSchema = z.object({
|
|
835
1041
|
definition_format: z.literal(CANONICAL_FORMAT),
|
|
836
|
-
namespace: identifier2.max(41),
|
|
1042
|
+
namespace: identifier2.max(41).regex(NAMESPACE_PATTERN, "invalid or reserved ORM namespace"),
|
|
837
1043
|
description: z.string().max(2e3).optional(),
|
|
838
1044
|
enums: z.array(canonicalEnumSchema).default([]),
|
|
839
1045
|
tables: z.array(canonicalTableSchema).min(0).max(500),
|
|
@@ -867,6 +1073,7 @@ var localDefinitionSchema = z.object({
|
|
|
867
1073
|
}
|
|
868
1074
|
}
|
|
869
1075
|
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1076
|
+
validateUnkeyedLinkIdentities({ definition, ctx });
|
|
870
1077
|
const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
|
|
871
1078
|
for (const name2 of Object.keys(definition.exports ?? {})) {
|
|
872
1079
|
if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
|
|
@@ -1189,7 +1396,7 @@ var limitIrSchema = z.object({
|
|
|
1189
1396
|
variable: z.string().optional(),
|
|
1190
1397
|
value: z.number().int().positive().optional(),
|
|
1191
1398
|
default: z.number().int().positive(),
|
|
1192
|
-
max: z.number().int().positive()
|
|
1399
|
+
max: z.number().int().positive().max(MAX_PERSISTED_READ_ROWS)
|
|
1193
1400
|
});
|
|
1194
1401
|
var selectionIrSchema = z.lazy(
|
|
1195
1402
|
() => z.object({
|
|
@@ -1854,6 +2061,7 @@ var Parser2 = class {
|
|
|
1854
2061
|
return node;
|
|
1855
2062
|
}
|
|
1856
2063
|
if (this.peekKeyword("exists")) return this.parseExists();
|
|
2064
|
+
if (this.peekKeyword("visible") && this.tokens[this.index + 1]?.kind === "lparen") return this.parseVisible();
|
|
1857
2065
|
return this.parseComparison();
|
|
1858
2066
|
}
|
|
1859
2067
|
parseBooleanCall(fn) {
|
|
@@ -1873,6 +2081,13 @@ var Parser2 = class {
|
|
|
1873
2081
|
this.expect("rparen", "')' closing exists(...)");
|
|
1874
2082
|
return { kind: "exists", path, where };
|
|
1875
2083
|
}
|
|
2084
|
+
parseVisible() {
|
|
2085
|
+
this.advance();
|
|
2086
|
+
this.expect("lparen", "'(' after visible");
|
|
2087
|
+
const path = this.parsePath("a link path in visible(...)");
|
|
2088
|
+
this.expect("rparen", "')' closing visible(...)");
|
|
2089
|
+
return { kind: "visible", path };
|
|
2090
|
+
}
|
|
1876
2091
|
parseComparison() {
|
|
1877
2092
|
const left = this.parseOperand();
|
|
1878
2093
|
const operator = this.expect("operator", "a comparison operator");
|
|
@@ -1988,6 +2203,24 @@ function parsePolicy(source) {
|
|
|
1988
2203
|
|
|
1989
2204
|
// src/predicate-v2/compile.ts
|
|
1990
2205
|
var POLICY_MAX_LINK_DEPTH = 3;
|
|
2206
|
+
function scopeToNamespace(pred, namespace) {
|
|
2207
|
+
switch (pred.kind) {
|
|
2208
|
+
case "capability":
|
|
2209
|
+
case "role":
|
|
2210
|
+
return pred.namespace ? pred : { ...pred, namespace };
|
|
2211
|
+
case "and":
|
|
2212
|
+
case "or":
|
|
2213
|
+
return { ...pred, operands: pred.operands.map((operand) => scopeToNamespace(operand, namespace)) };
|
|
2214
|
+
case "not":
|
|
2215
|
+
return { ...pred, operand: scopeToNamespace(pred.operand, namespace) };
|
|
2216
|
+
case "exists":
|
|
2217
|
+
return { ...pred, where: scopeToNamespace(pred.where, namespace) };
|
|
2218
|
+
case "aggPredicate":
|
|
2219
|
+
return pred.where ? { ...pred, where: scopeToNamespace(pred.where, namespace) } : pred;
|
|
2220
|
+
default:
|
|
2221
|
+
return pred;
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
1991
2224
|
var CMP_BY_OP = {
|
|
1992
2225
|
"=": "eq",
|
|
1993
2226
|
"!=": "neq",
|
|
@@ -2021,7 +2254,7 @@ function resolveLink(info, ref) {
|
|
|
2021
2254
|
function columnCandidates(info) {
|
|
2022
2255
|
return [...info.columns.map((c) => c.name), ...info.links.map((l) => l.name)];
|
|
2023
2256
|
}
|
|
2024
|
-
var PolicyCompiler = class {
|
|
2257
|
+
var PolicyCompiler = class _PolicyCompiler {
|
|
2025
2258
|
constructor(ctx) {
|
|
2026
2259
|
this.ctx = ctx;
|
|
2027
2260
|
}
|
|
@@ -2034,13 +2267,15 @@ var PolicyCompiler = class {
|
|
|
2034
2267
|
return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
|
|
2035
2268
|
case "not":
|
|
2036
2269
|
assertNoNegatedExternalClaim(node.operand);
|
|
2037
|
-
return { kind: "not", operand: this.compile(node.operand, table, depth) };
|
|
2270
|
+
return { kind: "not", operand: new _PolicyCompiler({ ...this.ctx, negated: true }).compile(node.operand, table, depth) };
|
|
2038
2271
|
case "capability":
|
|
2039
2272
|
return { kind: "capability", key: node.key };
|
|
2040
2273
|
case "role":
|
|
2041
2274
|
return { kind: "role", key: node.key };
|
|
2042
2275
|
case "exists":
|
|
2043
2276
|
return this.compileExists(node.path, node.where, table, depth);
|
|
2277
|
+
case "visible":
|
|
2278
|
+
return this.compileVisible(node.path, table, depth);
|
|
2044
2279
|
case "compare":
|
|
2045
2280
|
return this.compileCompare(node, table, depth);
|
|
2046
2281
|
}
|
|
@@ -2054,6 +2289,36 @@ var PolicyCompiler = class {
|
|
|
2054
2289
|
}
|
|
2055
2290
|
return pred;
|
|
2056
2291
|
}
|
|
2292
|
+
/** `visible(path)` → `exists(path, <final target's read policy>)`. */
|
|
2293
|
+
compileVisible(path, table, depth) {
|
|
2294
|
+
const { links, finalTable } = this.resolveLinkPath(path, table, depth, { toOneOnly: false });
|
|
2295
|
+
const readPolicy = this.ctx.resolver.readPolicy;
|
|
2296
|
+
if (!readPolicy) {
|
|
2297
|
+
throw new PolicyCompileError(
|
|
2298
|
+
`visible(${path.join(".")}) needs the target's read policy, which this compile context does not provide`,
|
|
2299
|
+
"visible-unresolvable"
|
|
2300
|
+
);
|
|
2301
|
+
}
|
|
2302
|
+
if (this.ctx.visiting.includes(finalTable)) {
|
|
2303
|
+
throw new PolicyCompileError(
|
|
2304
|
+
`visible(${path.join(".")}) on '${table}' reaches '${finalTable}' whose read policy is already being expanded (${[...this.ctx.visiting, finalTable].join(" \u2192 ")})`,
|
|
2305
|
+
"visible-cycle"
|
|
2306
|
+
);
|
|
2307
|
+
}
|
|
2308
|
+
const expression = readPolicy(finalTable);
|
|
2309
|
+
let pred = expression === void 0 ? { kind: "const", value: true } : compilePolicyAt({
|
|
2310
|
+
expression,
|
|
2311
|
+
table: finalTable,
|
|
2312
|
+
ctx: { ...this.ctx, visiting: [...this.ctx.visiting, finalTable] },
|
|
2313
|
+
depth: depth + links.length
|
|
2314
|
+
});
|
|
2315
|
+
const owner = this.ctx.resolver.policyNamespace?.(finalTable);
|
|
2316
|
+
if (owner) pred = scopeToNamespace(pred, owner);
|
|
2317
|
+
for (let i = links.length - 1; i >= 0; i -= 1) {
|
|
2318
|
+
pred = { kind: "exists", link: links[i].name, where: pred };
|
|
2319
|
+
}
|
|
2320
|
+
return pred;
|
|
2321
|
+
}
|
|
2057
2322
|
resolveLinkPath(path, table, depth, { toOneOnly }) {
|
|
2058
2323
|
const links = [];
|
|
2059
2324
|
let current = table;
|
|
@@ -2114,6 +2379,7 @@ var PolicyCompiler = class {
|
|
|
2114
2379
|
);
|
|
2115
2380
|
}
|
|
2116
2381
|
if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
|
|
2382
|
+
if (this.ctx.negated) throw negatedExternalClaim(valueSide.key);
|
|
2117
2383
|
assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
|
|
2118
2384
|
}
|
|
2119
2385
|
let cmp = this.buildCmp(op, column, valueSide);
|
|
@@ -2151,22 +2417,30 @@ function assertExternalClaimComparison({ op, key, column }) {
|
|
|
2151
2417
|
);
|
|
2152
2418
|
}
|
|
2153
2419
|
}
|
|
2420
|
+
function negatedExternalClaim(key) {
|
|
2421
|
+
return new PolicyCompileError(
|
|
2422
|
+
`claim('${key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
|
|
2423
|
+
"unsupported-comparison"
|
|
2424
|
+
);
|
|
2425
|
+
}
|
|
2154
2426
|
function assertNoNegatedExternalClaim(operand) {
|
|
2155
2427
|
walkPolicy(operand, (node) => {
|
|
2156
2428
|
if (node.kind !== "compare") return;
|
|
2157
2429
|
const claim = [node.left, node.right].find((side) => side.kind === "claim" && isExternalPolicyClaimKey(side.key));
|
|
2158
|
-
if (claim?.kind === "claim")
|
|
2159
|
-
throw new PolicyCompileError(
|
|
2160
|
-
`claim('${claim.key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
|
|
2161
|
-
"unsupported-comparison"
|
|
2162
|
-
);
|
|
2163
|
-
}
|
|
2430
|
+
if (claim?.kind === "claim") throw negatedExternalClaim(claim.key);
|
|
2164
2431
|
});
|
|
2165
2432
|
}
|
|
2166
2433
|
function compilePredicateV2({ expression, table, resolver, maxLinkDepth }) {
|
|
2434
|
+
return compilePredicateAt({
|
|
2435
|
+
expression,
|
|
2436
|
+
table,
|
|
2437
|
+
depth: 0,
|
|
2438
|
+
ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: [table] }
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
function compilePredicateAt({ expression, table, ctx, depth }) {
|
|
2167
2442
|
const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
|
|
2168
|
-
|
|
2169
|
-
return compiler.compile(parsed.ast, table, 0);
|
|
2443
|
+
return new PolicyCompiler(ctx).compile(parsed.ast, table, depth);
|
|
2170
2444
|
}
|
|
2171
2445
|
function assertClaimsOnlyWhen(parsed) {
|
|
2172
2446
|
walkPolicy(parsed.ast, (node) => {
|
|
@@ -2183,25 +2457,34 @@ function assertClaimsOnlyWhen(parsed) {
|
|
|
2183
2457
|
"when-not-claims-only"
|
|
2184
2458
|
);
|
|
2185
2459
|
}
|
|
2186
|
-
if (node.kind === "exists") {
|
|
2460
|
+
if (node.kind === "exists" || node.kind === "visible") {
|
|
2187
2461
|
throw new PolicyCompileError(
|
|
2188
|
-
|
|
2462
|
+
`variant 'when' guards are claims-only \u2014 ${node.kind}() is not allowed`,
|
|
2189
2463
|
"when-not-claims-only"
|
|
2190
2464
|
);
|
|
2191
2465
|
}
|
|
2192
2466
|
});
|
|
2193
2467
|
}
|
|
2194
|
-
function
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2468
|
+
function initialVisiting({ table, access }) {
|
|
2469
|
+
return (access ?? "read") === "read" ? [table] : [];
|
|
2470
|
+
}
|
|
2471
|
+
function compilePolicy({ expression, table, resolver, maxLinkDepth, access }) {
|
|
2472
|
+
return compilePolicyAt({
|
|
2473
|
+
expression,
|
|
2474
|
+
table,
|
|
2475
|
+
depth: 0,
|
|
2476
|
+
ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: initialVisiting({ table, access }) }
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
function compilePolicyAt({ expression, table, ctx, depth }) {
|
|
2480
|
+
if (typeof expression === "string") return compilePredicateAt({ expression, table, ctx, depth });
|
|
2481
|
+
const defaultPred = compilePredicateAt({ expression: expression.default, table, ctx, depth });
|
|
2199
2482
|
if (expression.variants.length === 0) return defaultPred;
|
|
2200
2483
|
const branches = expression.variants.map((variant) => {
|
|
2201
2484
|
const parsedWhen = parsePolicy(variant.when);
|
|
2202
2485
|
assertClaimsOnlyWhen(parsedWhen);
|
|
2203
|
-
const when =
|
|
2204
|
-
const grant = variant.allow === true ? { kind: "const", value: true } :
|
|
2486
|
+
const when = compilePredicateAt({ expression: parsedWhen, table, ctx, depth });
|
|
2487
|
+
const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateAt({ expression: variant.policy, table, ctx, depth });
|
|
2205
2488
|
return { kind: "and", operands: [when, grant] };
|
|
2206
2489
|
});
|
|
2207
2490
|
return { kind: "or", operands: [defaultPred, ...branches] };
|
|
@@ -2417,12 +2700,6 @@ var PolicyInjector = class {
|
|
|
2417
2700
|
if (root.op === "resolvedWrite") return this.injectResolvedWrite(root, returning);
|
|
2418
2701
|
if (root.op === "insert" || root.op === "insertOne") {
|
|
2419
2702
|
const insertPolicy = this.insert(table);
|
|
2420
|
-
if (predContainsExists(insertPolicy)) {
|
|
2421
|
-
throw new DocumentCompileError(
|
|
2422
|
-
`table '${table}' cannot be inserted into: v2.0 insert checks are claims + inserted-values only, but its insert policy traverses a link (exists)`,
|
|
2423
|
-
"insert-check-unrepresentable"
|
|
2424
|
-
);
|
|
2425
|
-
}
|
|
2426
2703
|
const pins = (this.ctx.pins.get(table) ?? []).map(
|
|
2427
2704
|
(pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
|
|
2428
2705
|
);
|
|
@@ -2437,22 +2714,14 @@ var PolicyInjector = class {
|
|
|
2437
2714
|
const updatePolicy = this.update(table);
|
|
2438
2715
|
const where = andPred(updatePolicy, root.where ? this.injectDocPred(root.where, table) : void 0);
|
|
2439
2716
|
if (root.op === "update" || root.op === "updateByPk") {
|
|
2440
|
-
|
|
2441
|
-
return { ...root, where, ...check, returning };
|
|
2717
|
+
return { ...root, where, insertCheck: updatePolicy, returning };
|
|
2442
2718
|
}
|
|
2443
2719
|
if (root.op === "cowOverride") {
|
|
2444
2720
|
const insertPolicy = this.insert(table);
|
|
2445
|
-
if (predContainsExists(insertPolicy)) {
|
|
2446
|
-
throw new DocumentCompileError(
|
|
2447
|
-
`table '${table}' cannot be copy-on-write overridden: v2.0 insert checks are claims + inserted-values only, but its insert policy traverses a link (exists)`,
|
|
2448
|
-
"insert-check-unrepresentable"
|
|
2449
|
-
);
|
|
2450
|
-
}
|
|
2451
2721
|
const pins = (this.ctx.pins.get(table) ?? []).map(
|
|
2452
2722
|
(pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
|
|
2453
2723
|
);
|
|
2454
|
-
|
|
2455
|
-
return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, ...check, returning };
|
|
2724
|
+
return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, insertCheck: updatePolicy, returning };
|
|
2456
2725
|
}
|
|
2457
2726
|
return { ...root, where, returning };
|
|
2458
2727
|
}
|
|
@@ -2674,7 +2943,7 @@ function scopePolicy(pred, namespace) {
|
|
|
2674
2943
|
switch (pred.kind) {
|
|
2675
2944
|
case "capability":
|
|
2676
2945
|
case "role":
|
|
2677
|
-
return { ...pred, namespace };
|
|
2946
|
+
return pred.namespace ? pred : { ...pred, namespace };
|
|
2678
2947
|
case "and":
|
|
2679
2948
|
case "or":
|
|
2680
2949
|
return { ...pred, operands: pred.operands.map((p) => scopePolicy(p, namespace)) };
|
|
@@ -2764,6 +3033,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
2764
3033
|
const platformMeta = usesPlatformUsers ? platformUserMeta() : void 0;
|
|
2765
3034
|
if (platformMeta) tables[PLATFORM_USER_TABLE] = platformMeta;
|
|
2766
3035
|
const syncedTableNames = new Set(canonical.tables.filter((t) => t.source !== void 0).map((t) => t.table));
|
|
3036
|
+
const unkeyedSyncedTableNames = new Set(relations.filter((table) => table.source !== void 0 && table.primaryKey?.length === 0).map((table) => table.table));
|
|
2767
3037
|
const viewDependencies = new Map((canonical.views ?? []).map((view) => [view.view, view.dependencies]));
|
|
2768
3038
|
const claimsGatedMemo = /* @__PURE__ */ new Map();
|
|
2769
3039
|
const relationIsClaimsGated = (name2) => {
|
|
@@ -2777,6 +3047,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
2777
3047
|
};
|
|
2778
3048
|
for (const table of relations) {
|
|
2779
3049
|
const isView = viewNames.has(table.table);
|
|
3050
|
+
const readOnly = isView || unkeyedSyncedTableNames.has(table.table);
|
|
2780
3051
|
const owner = owners.get(table.table);
|
|
2781
3052
|
const entries = resolveColumnEntries(table, enums, diagnostics);
|
|
2782
3053
|
columnEntries.set(table.table, entries);
|
|
@@ -2824,13 +3095,13 @@ function buildCompileContext(rawCanonical) {
|
|
|
2824
3095
|
// are claims-gated — see tableMetaIrSchema.claimsGated.
|
|
2825
3096
|
claimsGated: relationIsClaimsGated(table.table),
|
|
2826
3097
|
columns: entries.map(({ deprecated: _deprecated, onUpdateFn: _onUpdateFn, ...column }) => column),
|
|
2827
|
-
primaryKey: isView ? primaryKey : primaryKey.length > 0 ? primaryKey : ["id"],
|
|
3098
|
+
primaryKey: isView || unkeyedSyncedTableNames.has(table.table) ? primaryKey : primaryKey.length > 0 ? primaryKey : ["id"],
|
|
2828
3099
|
links: [],
|
|
2829
3100
|
filterable: data.map((c) => c.name),
|
|
2830
3101
|
orderable: data.filter((c) => c.type !== "jsonb").map((c) => c.name),
|
|
2831
|
-
insertable: resolved ? resolvedWritable(data) :
|
|
2832
|
-
settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey.includes(name2)) :
|
|
2833
|
-
appendable:
|
|
3102
|
+
insertable: resolved ? resolvedWritable(data) : readOnly ? [] : active.map((c) => c.name),
|
|
3103
|
+
settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey.includes(name2)) : readOnly ? [] : active.filter((c) => !c.onUpdateFn).map((c) => c.name),
|
|
3104
|
+
appendable: readOnly ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
|
|
2834
3105
|
...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
|
|
2835
3106
|
...cells ? { cells } : {},
|
|
2836
3107
|
...resolved ? { resolved: resolvedMeta({ resolved, pk: primaryKey[0] }) } : {},
|
|
@@ -2854,6 +3125,16 @@ function buildCompileContext(rawCanonical) {
|
|
|
2854
3125
|
});
|
|
2855
3126
|
continue;
|
|
2856
3127
|
}
|
|
3128
|
+
const identityTable = link.kind === "toOne" ? targetName : link.kind === "toMany" ? table.table : void 0;
|
|
3129
|
+
if (identityTable !== void 0 && unkeyedSyncedTableNames.has(identityTable)) {
|
|
3130
|
+
diagnostics.push({
|
|
3131
|
+
severity: "error",
|
|
3132
|
+
code: "unkeyed-link-identity",
|
|
3133
|
+
table: table.table,
|
|
3134
|
+
message: `link '${link.name}' cannot use unkeyed table '${identityTable}' as a row identity`
|
|
3135
|
+
});
|
|
3136
|
+
continue;
|
|
3137
|
+
}
|
|
2857
3138
|
const targetEntries = targetName === PLATFORM_USER_TABLE ? target.columns.map((c) => ({ ...c, deprecated: false, onUpdateFn: false })) : columnEntries.get(targetName);
|
|
2858
3139
|
const onEntries = link.kind === "toMany" ? targetEntries : entries;
|
|
2859
3140
|
const referencesEntries = link.kind === "toMany" ? entries : targetEntries;
|
|
@@ -2883,7 +3164,14 @@ function buildCompileContext(rawCanonical) {
|
|
|
2883
3164
|
const updatePolicies = /* @__PURE__ */ new Map();
|
|
2884
3165
|
const pins = /* @__PURE__ */ new Map();
|
|
2885
3166
|
const splitWritePolicyTables = /* @__PURE__ */ new Set();
|
|
2886
|
-
const
|
|
3167
|
+
const readPolicyExpressions = new Map(relations.map((relation) => [relation.table, relation.policies.read]));
|
|
3168
|
+
const resolver = {
|
|
3169
|
+
tables,
|
|
3170
|
+
readPolicy: (table) => readPolicyExpressions.get(table),
|
|
3171
|
+
// In a composed namespace, an imported table's read policy (inlined by
|
|
3172
|
+
// visible()) is checked against the namespace that owns it.
|
|
3173
|
+
...normalized.imports?.length ? { policyNamespace: (table) => owners.get(table)?.namespace } : {}
|
|
3174
|
+
};
|
|
2887
3175
|
for (const table of relations) {
|
|
2888
3176
|
const isView = viewNames.has(table.table);
|
|
2889
3177
|
const compilePolicyFor = (access, store) => {
|
|
@@ -2892,7 +3180,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
2892
3180
|
return;
|
|
2893
3181
|
}
|
|
2894
3182
|
try {
|
|
2895
|
-
const pred = compilePolicy({ expression, table: table.table, resolver });
|
|
3183
|
+
const pred = compilePolicy({ expression, table: table.table, resolver, access });
|
|
2896
3184
|
store.set(table.table, normalized.imports?.length ? scopePolicy(pred, owners.get(table.table).namespace) : pred);
|
|
2897
3185
|
} catch (error) {
|
|
2898
3186
|
if (error instanceof PolicyCompileError || error instanceof PolicyParseError) {
|
|
@@ -2912,7 +3200,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
2912
3200
|
compilePolicyFor("write", writePolicies);
|
|
2913
3201
|
compilePolicyFor("insert", insertPolicies);
|
|
2914
3202
|
compilePolicyFor("update", updatePolicies);
|
|
2915
|
-
if (isView && table.resolved === void 0) {
|
|
3203
|
+
if (isView && table.resolved === void 0 || unkeyedSyncedTableNames.has(table.table)) {
|
|
2916
3204
|
writePolicies.set(table.table, CONST_FALSE);
|
|
2917
3205
|
insertPolicies.set(table.table, CONST_FALSE);
|
|
2918
3206
|
updatePolicies.set(table.table, CONST_FALSE);
|
|
@@ -2987,7 +3275,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
2987
3275
|
const queryRoots = /* @__PURE__ */ new Map();
|
|
2988
3276
|
const mutationRoots = /* @__PURE__ */ new Map();
|
|
2989
3277
|
for (const table of relations) {
|
|
2990
|
-
if (table.internal === true) continue;
|
|
3278
|
+
if (table.internal === true || unkeyedSyncedTableNames.has(table.table)) continue;
|
|
2991
3279
|
const roots = queryRootNames(table.table);
|
|
2992
3280
|
queryRoots.set(roots.select, { kind: "select", table: table.table });
|
|
2993
3281
|
if ((tables[table.table]?.primaryKey.length ?? 0) > 0) {
|
|
@@ -3367,8 +3655,10 @@ var OperationLowerer = class {
|
|
|
3367
3655
|
constructor(ctx, restricted) {
|
|
3368
3656
|
this.ctx = ctx;
|
|
3369
3657
|
this.variables = new Map(restricted.variables.map((v) => [v.name, v]));
|
|
3658
|
+
this.maxRows = restricted.readBudget ?? MAX_LIMIT;
|
|
3370
3659
|
}
|
|
3371
3660
|
variables;
|
|
3661
|
+
maxRows;
|
|
3372
3662
|
meta(table) {
|
|
3373
3663
|
return this.ctx.tables[table];
|
|
3374
3664
|
}
|
|
@@ -3572,13 +3862,13 @@ var OperationLowerer = class {
|
|
|
3572
3862
|
});
|
|
3573
3863
|
}
|
|
3574
3864
|
lowerLimit(node) {
|
|
3575
|
-
const base2 = { default: DEFAULT_LIMIT, max:
|
|
3865
|
+
const base2 = { default: DEFAULT_LIMIT, max: this.maxRows };
|
|
3576
3866
|
if (!node) return base2;
|
|
3577
3867
|
if (node.kind === Kind.VARIABLE) return { ...base2, variable: node.name.value };
|
|
3578
3868
|
if (node.kind === Kind.INT) {
|
|
3579
3869
|
const value = Number(node.value);
|
|
3580
3870
|
if (value < 1) fail2("limit must be a positive integer", "invalid-limit");
|
|
3581
|
-
return { ...base2, value: Math.min(value,
|
|
3871
|
+
return { ...base2, value: Math.min(value, this.maxRows) };
|
|
3582
3872
|
}
|
|
3583
3873
|
fail2("limit must be an integer literal or a variable", "invalid-limit");
|
|
3584
3874
|
}
|
|
@@ -4377,7 +4667,13 @@ var OperationLowerer = class {
|
|
|
4377
4667
|
};
|
|
4378
4668
|
function lowerOperation({ restricted, ctx }) {
|
|
4379
4669
|
const lowerer = new OperationLowerer(ctx, restricted);
|
|
4380
|
-
|
|
4670
|
+
const roots = lowerer.lowerRoots(restricted.definition, restricted.kind);
|
|
4671
|
+
if (restricted.readBudget === void 0) return roots;
|
|
4672
|
+
const root = roots[0];
|
|
4673
|
+
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) {
|
|
4674
|
+
fail2("@readBudget requires one flat select root without related selections, aggregates, cells, or prefetch", "invalid-read-budget");
|
|
4675
|
+
}
|
|
4676
|
+
return roots;
|
|
4381
4677
|
}
|
|
4382
4678
|
function isCellColumn(meta, name2) {
|
|
4383
4679
|
if (!meta.cells) return false;
|
|
@@ -4436,6 +4732,19 @@ function parseDocuments({
|
|
|
4436
4732
|
}
|
|
4437
4733
|
return { documents: parsed, diagnostics };
|
|
4438
4734
|
}
|
|
4735
|
+
function parseReadBudget(operation) {
|
|
4736
|
+
const directives = operation.directives?.filter((directive) => directive.name.value === "readBudget") ?? [];
|
|
4737
|
+
if (directives.length === 0) return void 0;
|
|
4738
|
+
const value = directives[0]?.arguments?.find((argument) => argument.name.value === "maxRows")?.value;
|
|
4739
|
+
if (operation.operation !== "query" || directives.length !== 1 || value?.kind !== Kind.INT) {
|
|
4740
|
+
throw new DocumentCompileError("@readBudget requires a literal maxRows on a query", "invalid-read-budget");
|
|
4741
|
+
}
|
|
4742
|
+
const maxRows = Number(value.value);
|
|
4743
|
+
if (!Number.isSafeInteger(maxRows) || maxRows < 1 || maxRows > MAX_PERSISTED_READ_ROWS) {
|
|
4744
|
+
throw new DocumentCompileError(`@readBudget(maxRows:) must be between 1 and ${MAX_PERSISTED_READ_ROWS}`, "invalid-read-budget");
|
|
4745
|
+
}
|
|
4746
|
+
return maxRows;
|
|
4747
|
+
}
|
|
4439
4748
|
function unwrapType(node, variable) {
|
|
4440
4749
|
let nullable = true;
|
|
4441
4750
|
let list2 = false;
|
|
@@ -4596,6 +4905,7 @@ function restrictOperation({
|
|
|
4596
4905
|
name: name2,
|
|
4597
4906
|
kind: operation.operation === "mutation" ? "mutation" : "query",
|
|
4598
4907
|
requires: parseRequires(operation),
|
|
4908
|
+
readBudget: parseReadBudget(operation),
|
|
4599
4909
|
variables,
|
|
4600
4910
|
definition: operation
|
|
4601
4911
|
};
|
|
@@ -5636,6 +5946,118 @@ function bindNamedParameters({ sql, names }) {
|
|
|
5636
5946
|
}
|
|
5637
5947
|
return result;
|
|
5638
5948
|
}
|
|
5949
|
+
var parser2;
|
|
5950
|
+
var functions = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
|
|
5951
|
+
var scalarTypes = /* @__PURE__ */ new Set([
|
|
5952
|
+
"bool",
|
|
5953
|
+
"boolean",
|
|
5954
|
+
"int2",
|
|
5955
|
+
"int4",
|
|
5956
|
+
"int8",
|
|
5957
|
+
"integer",
|
|
5958
|
+
"bigint",
|
|
5959
|
+
"smallint",
|
|
5960
|
+
"numeric",
|
|
5961
|
+
"decimal",
|
|
5962
|
+
"float4",
|
|
5963
|
+
"float8",
|
|
5964
|
+
"real",
|
|
5965
|
+
"text",
|
|
5966
|
+
"varchar",
|
|
5967
|
+
"bpchar",
|
|
5968
|
+
"uuid",
|
|
5969
|
+
"date",
|
|
5970
|
+
"timestamp",
|
|
5971
|
+
"timestamptz",
|
|
5972
|
+
"time",
|
|
5973
|
+
"timetz",
|
|
5974
|
+
"interval",
|
|
5975
|
+
"json",
|
|
5976
|
+
"jsonb",
|
|
5977
|
+
"bytea"
|
|
5978
|
+
]);
|
|
5979
|
+
var sqlValues = /* @__PURE__ */ new Set([
|
|
5980
|
+
"SVFOP_CURRENT_DATE",
|
|
5981
|
+
"SVFOP_CURRENT_TIME",
|
|
5982
|
+
"SVFOP_CURRENT_TIME_N",
|
|
5983
|
+
"SVFOP_CURRENT_TIMESTAMP",
|
|
5984
|
+
"SVFOP_CURRENT_TIMESTAMP_N",
|
|
5985
|
+
"SVFOP_LOCALTIME",
|
|
5986
|
+
"SVFOP_LOCALTIME_N",
|
|
5987
|
+
"SVFOP_LOCALTIMESTAMP",
|
|
5988
|
+
"SVFOP_LOCALTIMESTAMP_N",
|
|
5989
|
+
"SVFOP_CURRENT_ROLE",
|
|
5990
|
+
"SVFOP_CURRENT_USER",
|
|
5991
|
+
"SVFOP_USER",
|
|
5992
|
+
"SVFOP_SESSION_USER",
|
|
5993
|
+
"SVFOP_CURRENT_CATALOG",
|
|
5994
|
+
"SVFOP_CURRENT_SCHEMA"
|
|
5995
|
+
]);
|
|
5996
|
+
async function validateManagedViewQueries({ canonical }) {
|
|
5997
|
+
for (const definition of [canonical, ...canonical.imports ?? []]) {
|
|
5998
|
+
for (const view of definition.views ?? []) {
|
|
5999
|
+
const fail4 = (message) => {
|
|
6000
|
+
throw new OrmDefinitionError(`managed view '${definition.namespace}/${view.view}': ${message}`);
|
|
6001
|
+
};
|
|
6002
|
+
parser2 ??= new PgParser({ version: 17 });
|
|
6003
|
+
const parsed = await parser2.parse(view.query);
|
|
6004
|
+
if (parsed.error || !parsed.tree) fail4("invalid SELECT query");
|
|
6005
|
+
const statements = list(obj(parsed.tree).stmts);
|
|
6006
|
+
if (statements.length !== 1 || !obj(obj(statements[0]).stmt).SelectStmt) fail4("one SELECT query is required");
|
|
6007
|
+
validateTree({ tree: parsed.tree, schema: ormSchemaName(definition.namespace), enums: definition.enums.map((entry) => entry.name), fail: fail4 });
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
}
|
|
6011
|
+
function pgCatalogName(parts) {
|
|
6012
|
+
if (parts.length === 1) return parts[0];
|
|
6013
|
+
if (parts.length === 2 && parts[0] === "pg_catalog") return parts[1];
|
|
6014
|
+
return void 0;
|
|
6015
|
+
}
|
|
6016
|
+
function reviewedFunctionName({ parts, format }) {
|
|
6017
|
+
return format === "COERCE_SQL_SYNTAX" && parts.length === 2 && parts[0] === "pg_catalog" && parts[1] === "position" ? "strpos" : pgCatalogName(parts);
|
|
6018
|
+
}
|
|
6019
|
+
function validateTree({ tree, ...scope }) {
|
|
6020
|
+
const pending = [tree];
|
|
6021
|
+
while (pending.length) {
|
|
6022
|
+
const value = pending.pop();
|
|
6023
|
+
if (Array.isArray(value)) {
|
|
6024
|
+
pending.push(...value);
|
|
6025
|
+
continue;
|
|
6026
|
+
}
|
|
6027
|
+
if (!value || typeof value !== "object") continue;
|
|
6028
|
+
const node = obj(value);
|
|
6029
|
+
for (const [kind, child] of Object.entries(node)) {
|
|
6030
|
+
validateNode({ kind, child, scope });
|
|
6031
|
+
pending.push(child);
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
}
|
|
6035
|
+
function validateNode({ kind, child, scope }) {
|
|
6036
|
+
const { fail: fail4 } = scope;
|
|
6037
|
+
if (kind.endsWith("Stmt") && kind !== "SelectStmt") fail4("only SELECT statements are permitted");
|
|
6038
|
+
if (kind === "FuncCall") {
|
|
6039
|
+
const call = obj(child);
|
|
6040
|
+
const parts = strings(call.funcname);
|
|
6041
|
+
const name2 = reviewedFunctionName({ parts, format: call.funcformat });
|
|
6042
|
+
if (!name2 || !functions.has(name2)) fail4(`only explicitly allowlisted pg_catalog functions are permitted (found '${parts.join(".")}')`);
|
|
6043
|
+
}
|
|
6044
|
+
if (kind === "typeName" || kind === "TypeName") validateType({ node: obj(child), ...scope });
|
|
6045
|
+
if (kind === "A_Expr" && pgCatalogName(strings(obj(child).name)) === void 0) fail4("only pg_catalog operators are permitted");
|
|
6046
|
+
if (kind === "SortBy" && list(obj(child).useOp).length && pgCatalogName(strings(obj(child).useOp)) === void 0) fail4("only pg_catalog sort operators are permitted");
|
|
6047
|
+
if (kind === "SQLValueFunction" && !sqlValues.has(String(obj(child).op))) fail4("unsupported SQL value expression");
|
|
6048
|
+
if (kind === "ParamRef") fail4("view queries cannot contain parameters");
|
|
6049
|
+
if (kind === "A_Indirection" && list(obj(child).indirection).some((entry) => !obj(entry).A_Indices)) {
|
|
6050
|
+
fail4("composite field notation is not permitted in managed views");
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6053
|
+
function validateType({ node, schema, enums, fail: fail4 }) {
|
|
6054
|
+
const parts = strings(node.names);
|
|
6055
|
+
const builtin = pgCatalogName(parts);
|
|
6056
|
+
const managedEnum = parts.length === 1 && enums.includes(parts[0]) || parts.length === 2 && parts[0] === schema && enums.includes(parts[1]);
|
|
6057
|
+
if (node.setof || node.pct_type || !managedEnum && (!builtin || !scalarTypes.has(builtin))) {
|
|
6058
|
+
fail4(`casts may only use supported pg_catalog scalar types or declared enums (found '${parts.join(".")}')`);
|
|
6059
|
+
}
|
|
6060
|
+
}
|
|
5639
6061
|
|
|
5640
6062
|
// src/sql/operations.ts
|
|
5641
6063
|
function isSqlOperationDocument(path, contents) {
|
|
@@ -5651,7 +6073,13 @@ function isSqlOperationDocument(path, contents) {
|
|
|
5651
6073
|
async function compileOperationDocuments(input) {
|
|
5652
6074
|
const sqlDocuments = input.documents.filter((d) => isSqlOperationDocument(d.path, d.contents));
|
|
5653
6075
|
const result = compileNamespace({ ...input, documents: input.documents.filter((d) => !isSqlOperationDocument(d.path, d.contents)) });
|
|
5654
|
-
if (!result.ok
|
|
6076
|
+
if (!result.ok) return result;
|
|
6077
|
+
const viewDiagnostics = await managedViewDiagnostics(input.canonical);
|
|
6078
|
+
if (viewDiagnostics.length) return { ok: false, diagnostics: [...result.diagnostics, ...viewDiagnostics] };
|
|
6079
|
+
if (!sqlDocuments.length) return result;
|
|
6080
|
+
return compileSqlDocuments({ input, result, sqlDocuments });
|
|
6081
|
+
}
|
|
6082
|
+
async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
5655
6083
|
for (const document of sqlDocuments) {
|
|
5656
6084
|
try {
|
|
5657
6085
|
const definition = defineOperation(JSON.parse(document.contents));
|
|
@@ -5745,6 +6173,18 @@ async function compileOperationDocuments(input) {
|
|
|
5745
6173
|
result.manifest.opsHash = result.artifact.opsHash;
|
|
5746
6174
|
return { ...result, artifact: normalizeIr(result.artifact) };
|
|
5747
6175
|
}
|
|
6176
|
+
async function managedViewDiagnostics(canonical) {
|
|
6177
|
+
try {
|
|
6178
|
+
await validateManagedViewQueries({ canonical });
|
|
6179
|
+
return [];
|
|
6180
|
+
} catch (error) {
|
|
6181
|
+
return [{
|
|
6182
|
+
severity: "error",
|
|
6183
|
+
code: "invalid-view-query",
|
|
6184
|
+
message: error instanceof Error ? error.message : "invalid managed view query"
|
|
6185
|
+
}];
|
|
6186
|
+
}
|
|
6187
|
+
}
|
|
5748
6188
|
var parameterName = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(63).refine((value) => !["__proto__", "prototype", "constructor"].includes(value), "reserved parameter name");
|
|
5749
6189
|
var parameter = z.discriminatedUnion("type", [
|
|
5750
6190
|
z.object({ type: z.literal("text"), value: z.string().nullable() }).strict(),
|
|
@@ -5826,6 +6266,6 @@ function compileDatasetPreviews(input) {
|
|
|
5826
6266
|
return operations;
|
|
5827
6267
|
}
|
|
5828
6268
|
|
|
5829
|
-
export { COMPILER_MAJOR, CONST_TRUE, DATASET_PREVIEW_LIMIT, DEFAULT_LIMIT, DocumentCompileError, MAX_LIMIT, NamespaceCompileError, ORDER_BY_DIRECTIONS, OperationLowerer, PLATFORM_USER_TABLE, PLATFORM_USER_TYPENAME, POLICY_COMPARE_OPS, POLICY_MAX_LINK_DEPTH, PolicyCompileError, PolicyParseError, allowedBoolExpOperators, allowedOps, andPred, assertClaimsOnlyWhen, buildCompileContext, camelCase, canonicalOperationText, classifyInputType, collectPinnedColumns, compileDatasetPreviews, compileNamespace, compileNamespaceOrThrow, compileOperationDocuments, compilePolicy, compilePredicateV2, compileRuntimeSql, computeOpHash, computeOpsHash, computePinnedColumnsByTable, computeSchemaHash, defineOperation, enumTypeName, flattenOperation, injectPolicies, inputTypeName, isSqlOperationDocument, lowerOperation, mutationRootNames, parseDocuments, parsePolicy, parseSdl, pascalCase, platformUserMeta, predContains, predContainsExists, queryRootNames, restrictOperation, sqlOperationDefinitionSchema, sqlQueryInputSchema, typeNameForTable, validateAppendInput, validateBoolExpInput, validateInsertInput, validateOnConflictInput, validateOrderByInput, validateSelectColumns, validateSetInput, walkPolicy, walkPred };
|
|
6269
|
+
export { COMPILER_MAJOR, CONST_TRUE, DATASET_PREVIEW_LIMIT, DEFAULT_LIMIT, DocumentCompileError, MAX_LIMIT, NamespaceCompileError, ORDER_BY_DIRECTIONS, OperationLowerer, PLATFORM_USER_TABLE, PLATFORM_USER_TYPENAME, POLICY_COMPARE_OPS, POLICY_MAX_LINK_DEPTH, PolicyCompileError, PolicyParseError, allowedBoolExpOperators, allowedOps, andPred, assertClaimsOnlyWhen, buildCompileContext, camelCase, canonicalOperationText, classifyInputType, collectPinnedColumns, compileDatasetPreviews, compileNamespace, compileNamespaceOrThrow, compileOperationDocuments, compilePolicy, compilePredicateV2, compileRuntimeSql, computeOpHash, computeOpsHash, computePinnedColumnsByTable, computeSchemaHash, defineOperation, enumTypeName, flattenOperation, injectPolicies, inputTypeName, isSqlOperationDocument, lowerOperation, mutationRootNames, parseDocuments, parsePolicy, parseSdl, pascalCase, platformUserMeta, predContains, predContainsExists, queryRootNames, restrictOperation, sqlOperationDefinitionSchema, sqlQueryInputSchema, typeNameForTable, validateAppendInput, validateBoolExpInput, validateInsertInput, validateManagedViewQueries, validateOnConflictInput, validateOrderByInput, validateSelectColumns, validateSetInput, walkPolicy, walkPred };
|
|
5830
6270
|
//# sourceMappingURL=compiler.js.map
|
|
5831
6271
|
//# sourceMappingURL=compiler.js.map
|