@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/compiler.cjs
CHANGED
|
@@ -8,12 +8,17 @@ var pgParser = require('@supabase/pg-parser');
|
|
|
8
8
|
// src/compiler-gql/artifact.ts
|
|
9
9
|
|
|
10
10
|
// src/constants.ts
|
|
11
|
+
var NAMESPACE_PATTERN = /^(?!(?:events|native_sources)$)[a-z][a-z0-9_]{0,40}$/;
|
|
11
12
|
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
12
13
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
13
14
|
var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
|
|
15
|
+
var MAX_PERSISTED_READ_ROWS = 50001;
|
|
14
16
|
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
|
|
15
17
|
var ORM_SCHEMA_PREFIX = "orm_";
|
|
16
|
-
var ormSchemaName = (namespace) =>
|
|
18
|
+
var ormSchemaName = (namespace) => {
|
|
19
|
+
if (!NAMESPACE_PATTERN.test(namespace)) throw new Error(`invalid or reserved ORM namespace '${namespace}'`);
|
|
20
|
+
return `${ORM_SCHEMA_PREFIX}${namespace}`;
|
|
21
|
+
};
|
|
17
22
|
var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
|
|
18
23
|
var CELL_PART_SUFFIX = {
|
|
19
24
|
at: "__at",
|
|
@@ -389,6 +394,7 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
389
394
|
function validateSource({ scope, source, table }) {
|
|
390
395
|
const { fail: fail4 } = scope;
|
|
391
396
|
if (source.kind === "etl" && table.source === void 0) fail4(`etl source '${source.name}' must reference a synced table`);
|
|
397
|
+
if (source.kind === "etl" && table.primaryKey?.length === 0) fail4(`etl source '${source.name}' cannot use an unkeyed synced table for resolved row identity`);
|
|
392
398
|
if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
|
|
393
399
|
fail4(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
|
|
394
400
|
}
|
|
@@ -620,6 +626,163 @@ function isSingleViewQuerySql(value) {
|
|
|
620
626
|
}
|
|
621
627
|
return state === "normal" && !/\$[a-z_0-9]*\$/i.test(query);
|
|
622
628
|
}
|
|
629
|
+
var VIEW_QUERY_FUNCTION_ALLOWLIST = [
|
|
630
|
+
"abs",
|
|
631
|
+
"age",
|
|
632
|
+
"array_agg",
|
|
633
|
+
"array_append",
|
|
634
|
+
"array_cat",
|
|
635
|
+
"array_dims",
|
|
636
|
+
"array_length",
|
|
637
|
+
"array_lower",
|
|
638
|
+
"array_ndims",
|
|
639
|
+
"array_position",
|
|
640
|
+
"array_positions",
|
|
641
|
+
"array_prepend",
|
|
642
|
+
"array_remove",
|
|
643
|
+
"array_replace",
|
|
644
|
+
"array_to_string",
|
|
645
|
+
"array_trim",
|
|
646
|
+
"array_upper",
|
|
647
|
+
"ascii",
|
|
648
|
+
"avg",
|
|
649
|
+
"bit_and",
|
|
650
|
+
"bit_or",
|
|
651
|
+
"bit_xor",
|
|
652
|
+
"bool_and",
|
|
653
|
+
"bool_or",
|
|
654
|
+
"btrim",
|
|
655
|
+
"cardinality",
|
|
656
|
+
"cbrt",
|
|
657
|
+
"ceil",
|
|
658
|
+
"ceiling",
|
|
659
|
+
"char_length",
|
|
660
|
+
"character_length",
|
|
661
|
+
"chr",
|
|
662
|
+
"concat",
|
|
663
|
+
"concat_ws",
|
|
664
|
+
"count",
|
|
665
|
+
"cume_dist",
|
|
666
|
+
"date_bin",
|
|
667
|
+
"date_part",
|
|
668
|
+
"date_trunc",
|
|
669
|
+
"decode",
|
|
670
|
+
"degrees",
|
|
671
|
+
"dense_rank",
|
|
672
|
+
"encode",
|
|
673
|
+
"enum_first",
|
|
674
|
+
"enum_last",
|
|
675
|
+
"enum_range",
|
|
676
|
+
"every",
|
|
677
|
+
"exp",
|
|
678
|
+
"first_value",
|
|
679
|
+
"floor",
|
|
680
|
+
"format",
|
|
681
|
+
"grouping",
|
|
682
|
+
"initcap",
|
|
683
|
+
"json_agg",
|
|
684
|
+
"json_array_length",
|
|
685
|
+
"json_build_array",
|
|
686
|
+
"json_build_object",
|
|
687
|
+
"json_extract_path",
|
|
688
|
+
"json_extract_path_text",
|
|
689
|
+
"json_object_agg",
|
|
690
|
+
"json_object_keys",
|
|
691
|
+
"json_strip_nulls",
|
|
692
|
+
"json_typeof",
|
|
693
|
+
"jsonb_agg",
|
|
694
|
+
"jsonb_array_length",
|
|
695
|
+
"jsonb_build_array",
|
|
696
|
+
"jsonb_build_object",
|
|
697
|
+
"jsonb_extract_path",
|
|
698
|
+
"jsonb_extract_path_text",
|
|
699
|
+
"jsonb_insert",
|
|
700
|
+
"jsonb_object_agg",
|
|
701
|
+
"jsonb_object_keys",
|
|
702
|
+
"jsonb_path_exists",
|
|
703
|
+
"jsonb_path_match",
|
|
704
|
+
"jsonb_path_query",
|
|
705
|
+
"jsonb_path_query_array",
|
|
706
|
+
"jsonb_path_query_first",
|
|
707
|
+
"jsonb_pretty",
|
|
708
|
+
"jsonb_set",
|
|
709
|
+
"jsonb_set_lax",
|
|
710
|
+
"jsonb_strip_nulls",
|
|
711
|
+
"jsonb_typeof",
|
|
712
|
+
"justify_days",
|
|
713
|
+
"justify_hours",
|
|
714
|
+
"justify_interval",
|
|
715
|
+
"lag",
|
|
716
|
+
"last_value",
|
|
717
|
+
"lead",
|
|
718
|
+
"left",
|
|
719
|
+
"length",
|
|
720
|
+
"ln",
|
|
721
|
+
"log",
|
|
722
|
+
"lower",
|
|
723
|
+
"lpad",
|
|
724
|
+
"ltrim",
|
|
725
|
+
"make_date",
|
|
726
|
+
"make_interval",
|
|
727
|
+
"make_time",
|
|
728
|
+
"make_timestamp",
|
|
729
|
+
"make_timestamptz",
|
|
730
|
+
"max",
|
|
731
|
+
"md5",
|
|
732
|
+
"min",
|
|
733
|
+
"mod",
|
|
734
|
+
"nth_value",
|
|
735
|
+
"ntile",
|
|
736
|
+
"octet_length",
|
|
737
|
+
"percent_rank",
|
|
738
|
+
"pi",
|
|
739
|
+
"power",
|
|
740
|
+
"quote_ident",
|
|
741
|
+
"quote_literal",
|
|
742
|
+
"quote_nullable",
|
|
743
|
+
"radians",
|
|
744
|
+
"rank",
|
|
745
|
+
"regexp_count",
|
|
746
|
+
"regexp_instr",
|
|
747
|
+
"regexp_like",
|
|
748
|
+
"regexp_match",
|
|
749
|
+
"regexp_matches",
|
|
750
|
+
"regexp_replace",
|
|
751
|
+
"regexp_split_to_array",
|
|
752
|
+
"regexp_split_to_table",
|
|
753
|
+
"repeat",
|
|
754
|
+
"replace",
|
|
755
|
+
"reverse",
|
|
756
|
+
"right",
|
|
757
|
+
"round",
|
|
758
|
+
"row_number",
|
|
759
|
+
"rpad",
|
|
760
|
+
"rtrim",
|
|
761
|
+
"sign",
|
|
762
|
+
"split_part",
|
|
763
|
+
"sqrt",
|
|
764
|
+
"starts_with",
|
|
765
|
+
"string_agg",
|
|
766
|
+
"string_to_array",
|
|
767
|
+
"strpos",
|
|
768
|
+
"substr",
|
|
769
|
+
"substring",
|
|
770
|
+
"sum",
|
|
771
|
+
"timezone",
|
|
772
|
+
"to_ascii",
|
|
773
|
+
"to_char",
|
|
774
|
+
"to_date",
|
|
775
|
+
"to_hex",
|
|
776
|
+
"to_json",
|
|
777
|
+
"to_jsonb",
|
|
778
|
+
"to_number",
|
|
779
|
+
"to_timestamp",
|
|
780
|
+
"translate",
|
|
781
|
+
"trunc",
|
|
782
|
+
"unnest",
|
|
783
|
+
"upper",
|
|
784
|
+
"width_bucket"
|
|
785
|
+
];
|
|
623
786
|
var canonicalActionSchema = zod.z.object({
|
|
624
787
|
name: identifier2,
|
|
625
788
|
requires: zod.z.string().max(1e3).optional(),
|
|
@@ -680,7 +843,7 @@ var canonicalTableSchema = zod.z.object({
|
|
|
680
843
|
/** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
|
|
681
844
|
source: zod.z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
|
|
682
845
|
columns: zod.z.array(canonicalColumnSchema).default([]),
|
|
683
|
-
primaryKey: zod.z.array(columnIdentifier).
|
|
846
|
+
primaryKey: zod.z.array(columnIdentifier).max(8).optional(),
|
|
684
847
|
links: zod.z.array(canonicalLinkSchema).default([]),
|
|
685
848
|
indexes: zod.z.array(canonicalIndexSchema).default([]),
|
|
686
849
|
/** Omitted by old definitions, preserving their existing content hashes. */
|
|
@@ -716,6 +879,29 @@ var canonicalTableSchema = zod.z.object({
|
|
|
716
879
|
* data plane. Omitted otherwise.
|
|
717
880
|
*/
|
|
718
881
|
materializes: identifier2.optional()
|
|
882
|
+
}).superRefine((table, ctx) => {
|
|
883
|
+
if (table.primaryKey?.length !== 0) return;
|
|
884
|
+
const fail4 = (message, path = ["primaryKey"]) => ctx.addIssue({ code: "custom", path, message: `unkeyed table '${table.table}' ${message}` });
|
|
885
|
+
if (table.source === void 0 || table.columns.length === 0) {
|
|
886
|
+
fail4("requires a typed synced source");
|
|
887
|
+
}
|
|
888
|
+
if ([table.policies.write, table.policies.insert, table.policies.update].some((policy) => policy !== void 0)) {
|
|
889
|
+
fail4("is read-only and cannot declare write policies", ["policies"]);
|
|
890
|
+
}
|
|
891
|
+
const ownedCollections = [table.indexes, table.actions, table.checks, table.foreignKeys, table.cowMutations];
|
|
892
|
+
const ownedMetadata = [table.cellMetadata, table.internal, table.materializes, table.renamedFrom];
|
|
893
|
+
if (ownedCollections.some((items) => items !== void 0 && items.length > 0) || ownedMetadata.some((value) => value !== void 0)) {
|
|
894
|
+
fail4("cannot declare owned storage, constraints or identity operations");
|
|
895
|
+
}
|
|
896
|
+
for (const [index, column] of table.columns.entries()) {
|
|
897
|
+
const ownedColumnMetadata = [column.default, column.defaultFn, column.onUpdateFn, column.renamedFrom, column.cast, column.cell];
|
|
898
|
+
if (column.unique || ownedColumnMetadata.some((value) => value !== void 0)) {
|
|
899
|
+
fail4("cannot declare column uniqueness, mutation defaults or migration metadata", ["columns", index]);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
for (const [index, link] of table.links.entries()) {
|
|
903
|
+
if (link.enforce || link.kind === "toMany") fail4("cannot provide an identity for this link", ["links", index]);
|
|
904
|
+
}
|
|
719
905
|
});
|
|
720
906
|
var MAX_VIEW_QUERY = 1e5;
|
|
721
907
|
var MAX_RESOLVED_VIEW_QUERY = 5e6;
|
|
@@ -772,6 +958,26 @@ var canonicalViewSchema = zod.z.object({
|
|
|
772
958
|
}
|
|
773
959
|
}
|
|
774
960
|
});
|
|
961
|
+
function validateUnkeyedLinkIdentities({ definition, ctx }) {
|
|
962
|
+
const tables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
963
|
+
const relations = [
|
|
964
|
+
...definition.tables.map((table, index) => ({ kind: "tables", index, links: table.links })),
|
|
965
|
+
...(definition.views ?? []).map((view, index) => ({ kind: "views", index, links: view.links }))
|
|
966
|
+
];
|
|
967
|
+
for (const relation of relations) {
|
|
968
|
+
for (const [linkIndex, link] of relation.links.entries()) {
|
|
969
|
+
if (link.kind !== "toOne" || link.namespace !== void 0 && link.namespace !== definition.namespace) continue;
|
|
970
|
+
const target = link.target === void 0 ? void 0 : tables.get(link.target);
|
|
971
|
+
if (target?.source !== void 0 && target.primaryKey?.length === 0) {
|
|
972
|
+
ctx.addIssue({
|
|
973
|
+
code: "custom",
|
|
974
|
+
path: [relation.kind, relation.index, "links", linkIndex],
|
|
975
|
+
message: `link '${link.name}' cannot use unkeyed table '${target.table}' as a to-one identity`
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
775
981
|
var CELL_PART_TYPES = {
|
|
776
982
|
at: "timestamptz",
|
|
777
983
|
by: "text",
|
|
@@ -835,7 +1041,7 @@ var exportGateSchema = zod.z.union([zod.z.literal(true), zod.z.object({ capabili
|
|
|
835
1041
|
var relationExportSchema = zod.z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
|
|
836
1042
|
var localDefinitionSchema = zod.z.object({
|
|
837
1043
|
definition_format: zod.z.literal(CANONICAL_FORMAT),
|
|
838
|
-
namespace: identifier2.max(41),
|
|
1044
|
+
namespace: identifier2.max(41).regex(NAMESPACE_PATTERN, "invalid or reserved ORM namespace"),
|
|
839
1045
|
description: zod.z.string().max(2e3).optional(),
|
|
840
1046
|
enums: zod.z.array(canonicalEnumSchema).default([]),
|
|
841
1047
|
tables: zod.z.array(canonicalTableSchema).min(0).max(500),
|
|
@@ -869,6 +1075,7 @@ var localDefinitionSchema = zod.z.object({
|
|
|
869
1075
|
}
|
|
870
1076
|
}
|
|
871
1077
|
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1078
|
+
validateUnkeyedLinkIdentities({ definition, ctx });
|
|
872
1079
|
const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
|
|
873
1080
|
for (const name2 of Object.keys(definition.exports ?? {})) {
|
|
874
1081
|
if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
|
|
@@ -1191,7 +1398,7 @@ var limitIrSchema = zod.z.object({
|
|
|
1191
1398
|
variable: zod.z.string().optional(),
|
|
1192
1399
|
value: zod.z.number().int().positive().optional(),
|
|
1193
1400
|
default: zod.z.number().int().positive(),
|
|
1194
|
-
max: zod.z.number().int().positive()
|
|
1401
|
+
max: zod.z.number().int().positive().max(MAX_PERSISTED_READ_ROWS)
|
|
1195
1402
|
});
|
|
1196
1403
|
var selectionIrSchema = zod.z.lazy(
|
|
1197
1404
|
() => zod.z.object({
|
|
@@ -2828,6 +3035,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
2828
3035
|
const platformMeta = usesPlatformUsers ? platformUserMeta() : void 0;
|
|
2829
3036
|
if (platformMeta) tables[PLATFORM_USER_TABLE] = platformMeta;
|
|
2830
3037
|
const syncedTableNames = new Set(canonical.tables.filter((t) => t.source !== void 0).map((t) => t.table));
|
|
3038
|
+
const unkeyedSyncedTableNames = new Set(relations.filter((table) => table.source !== void 0 && table.primaryKey?.length === 0).map((table) => table.table));
|
|
2831
3039
|
const viewDependencies = new Map((canonical.views ?? []).map((view) => [view.view, view.dependencies]));
|
|
2832
3040
|
const claimsGatedMemo = /* @__PURE__ */ new Map();
|
|
2833
3041
|
const relationIsClaimsGated = (name2) => {
|
|
@@ -2841,6 +3049,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
2841
3049
|
};
|
|
2842
3050
|
for (const table of relations) {
|
|
2843
3051
|
const isView = viewNames.has(table.table);
|
|
3052
|
+
const readOnly = isView || unkeyedSyncedTableNames.has(table.table);
|
|
2844
3053
|
const owner = owners.get(table.table);
|
|
2845
3054
|
const entries = resolveColumnEntries(table, enums, diagnostics);
|
|
2846
3055
|
columnEntries.set(table.table, entries);
|
|
@@ -2888,13 +3097,13 @@ function buildCompileContext(rawCanonical) {
|
|
|
2888
3097
|
// are claims-gated — see tableMetaIrSchema.claimsGated.
|
|
2889
3098
|
claimsGated: relationIsClaimsGated(table.table),
|
|
2890
3099
|
columns: entries.map(({ deprecated: _deprecated, onUpdateFn: _onUpdateFn, ...column }) => column),
|
|
2891
|
-
primaryKey: isView ? primaryKey : primaryKey.length > 0 ? primaryKey : ["id"],
|
|
3100
|
+
primaryKey: isView || unkeyedSyncedTableNames.has(table.table) ? primaryKey : primaryKey.length > 0 ? primaryKey : ["id"],
|
|
2892
3101
|
links: [],
|
|
2893
3102
|
filterable: data.map((c) => c.name),
|
|
2894
3103
|
orderable: data.filter((c) => c.type !== "jsonb").map((c) => c.name),
|
|
2895
|
-
insertable: resolved ? resolvedWritable(data) :
|
|
2896
|
-
settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey.includes(name2)) :
|
|
2897
|
-
appendable:
|
|
3104
|
+
insertable: resolved ? resolvedWritable(data) : readOnly ? [] : active.map((c) => c.name),
|
|
3105
|
+
settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey.includes(name2)) : readOnly ? [] : active.filter((c) => !c.onUpdateFn).map((c) => c.name),
|
|
3106
|
+
appendable: readOnly ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
|
|
2898
3107
|
...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
|
|
2899
3108
|
...cells ? { cells } : {},
|
|
2900
3109
|
...resolved ? { resolved: resolvedMeta({ resolved, pk: primaryKey[0] }) } : {},
|
|
@@ -2918,6 +3127,16 @@ function buildCompileContext(rawCanonical) {
|
|
|
2918
3127
|
});
|
|
2919
3128
|
continue;
|
|
2920
3129
|
}
|
|
3130
|
+
const identityTable = link.kind === "toOne" ? targetName : link.kind === "toMany" ? table.table : void 0;
|
|
3131
|
+
if (identityTable !== void 0 && unkeyedSyncedTableNames.has(identityTable)) {
|
|
3132
|
+
diagnostics.push({
|
|
3133
|
+
severity: "error",
|
|
3134
|
+
code: "unkeyed-link-identity",
|
|
3135
|
+
table: table.table,
|
|
3136
|
+
message: `link '${link.name}' cannot use unkeyed table '${identityTable}' as a row identity`
|
|
3137
|
+
});
|
|
3138
|
+
continue;
|
|
3139
|
+
}
|
|
2921
3140
|
const targetEntries = targetName === PLATFORM_USER_TABLE ? target.columns.map((c) => ({ ...c, deprecated: false, onUpdateFn: false })) : columnEntries.get(targetName);
|
|
2922
3141
|
const onEntries = link.kind === "toMany" ? targetEntries : entries;
|
|
2923
3142
|
const referencesEntries = link.kind === "toMany" ? entries : targetEntries;
|
|
@@ -2983,7 +3202,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
2983
3202
|
compilePolicyFor("write", writePolicies);
|
|
2984
3203
|
compilePolicyFor("insert", insertPolicies);
|
|
2985
3204
|
compilePolicyFor("update", updatePolicies);
|
|
2986
|
-
if (isView && table.resolved === void 0) {
|
|
3205
|
+
if (isView && table.resolved === void 0 || unkeyedSyncedTableNames.has(table.table)) {
|
|
2987
3206
|
writePolicies.set(table.table, CONST_FALSE);
|
|
2988
3207
|
insertPolicies.set(table.table, CONST_FALSE);
|
|
2989
3208
|
updatePolicies.set(table.table, CONST_FALSE);
|
|
@@ -3058,7 +3277,7 @@ function buildCompileContext(rawCanonical) {
|
|
|
3058
3277
|
const queryRoots = /* @__PURE__ */ new Map();
|
|
3059
3278
|
const mutationRoots = /* @__PURE__ */ new Map();
|
|
3060
3279
|
for (const table of relations) {
|
|
3061
|
-
if (table.internal === true) continue;
|
|
3280
|
+
if (table.internal === true || unkeyedSyncedTableNames.has(table.table)) continue;
|
|
3062
3281
|
const roots = queryRootNames(table.table);
|
|
3063
3282
|
queryRoots.set(roots.select, { kind: "select", table: table.table });
|
|
3064
3283
|
if ((tables[table.table]?.primaryKey.length ?? 0) > 0) {
|
|
@@ -3438,8 +3657,10 @@ var OperationLowerer = class {
|
|
|
3438
3657
|
constructor(ctx, restricted) {
|
|
3439
3658
|
this.ctx = ctx;
|
|
3440
3659
|
this.variables = new Map(restricted.variables.map((v) => [v.name, v]));
|
|
3660
|
+
this.maxRows = restricted.readBudget ?? MAX_LIMIT;
|
|
3441
3661
|
}
|
|
3442
3662
|
variables;
|
|
3663
|
+
maxRows;
|
|
3443
3664
|
meta(table) {
|
|
3444
3665
|
return this.ctx.tables[table];
|
|
3445
3666
|
}
|
|
@@ -3643,13 +3864,13 @@ var OperationLowerer = class {
|
|
|
3643
3864
|
});
|
|
3644
3865
|
}
|
|
3645
3866
|
lowerLimit(node) {
|
|
3646
|
-
const base2 = { default: DEFAULT_LIMIT, max:
|
|
3867
|
+
const base2 = { default: DEFAULT_LIMIT, max: this.maxRows };
|
|
3647
3868
|
if (!node) return base2;
|
|
3648
3869
|
if (node.kind === graphql.Kind.VARIABLE) return { ...base2, variable: node.name.value };
|
|
3649
3870
|
if (node.kind === graphql.Kind.INT) {
|
|
3650
3871
|
const value = Number(node.value);
|
|
3651
3872
|
if (value < 1) fail2("limit must be a positive integer", "invalid-limit");
|
|
3652
|
-
return { ...base2, value: Math.min(value,
|
|
3873
|
+
return { ...base2, value: Math.min(value, this.maxRows) };
|
|
3653
3874
|
}
|
|
3654
3875
|
fail2("limit must be an integer literal or a variable", "invalid-limit");
|
|
3655
3876
|
}
|
|
@@ -4448,7 +4669,13 @@ var OperationLowerer = class {
|
|
|
4448
4669
|
};
|
|
4449
4670
|
function lowerOperation({ restricted, ctx }) {
|
|
4450
4671
|
const lowerer = new OperationLowerer(ctx, restricted);
|
|
4451
|
-
|
|
4672
|
+
const roots = lowerer.lowerRoots(restricted.definition, restricted.kind);
|
|
4673
|
+
if (restricted.readBudget === void 0) return roots;
|
|
4674
|
+
const root = roots[0];
|
|
4675
|
+
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) {
|
|
4676
|
+
fail2("@readBudget requires one flat select root without related selections, aggregates, cells, or prefetch", "invalid-read-budget");
|
|
4677
|
+
}
|
|
4678
|
+
return roots;
|
|
4452
4679
|
}
|
|
4453
4680
|
function isCellColumn(meta, name2) {
|
|
4454
4681
|
if (!meta.cells) return false;
|
|
@@ -4507,6 +4734,19 @@ function parseDocuments({
|
|
|
4507
4734
|
}
|
|
4508
4735
|
return { documents: parsed, diagnostics };
|
|
4509
4736
|
}
|
|
4737
|
+
function parseReadBudget(operation) {
|
|
4738
|
+
const directives = operation.directives?.filter((directive) => directive.name.value === "readBudget") ?? [];
|
|
4739
|
+
if (directives.length === 0) return void 0;
|
|
4740
|
+
const value = directives[0]?.arguments?.find((argument) => argument.name.value === "maxRows")?.value;
|
|
4741
|
+
if (operation.operation !== "query" || directives.length !== 1 || value?.kind !== graphql.Kind.INT) {
|
|
4742
|
+
throw new DocumentCompileError("@readBudget requires a literal maxRows on a query", "invalid-read-budget");
|
|
4743
|
+
}
|
|
4744
|
+
const maxRows = Number(value.value);
|
|
4745
|
+
if (!Number.isSafeInteger(maxRows) || maxRows < 1 || maxRows > MAX_PERSISTED_READ_ROWS) {
|
|
4746
|
+
throw new DocumentCompileError(`@readBudget(maxRows:) must be between 1 and ${MAX_PERSISTED_READ_ROWS}`, "invalid-read-budget");
|
|
4747
|
+
}
|
|
4748
|
+
return maxRows;
|
|
4749
|
+
}
|
|
4510
4750
|
function unwrapType(node, variable) {
|
|
4511
4751
|
let nullable = true;
|
|
4512
4752
|
let list2 = false;
|
|
@@ -4667,6 +4907,7 @@ function restrictOperation({
|
|
|
4667
4907
|
name: name2,
|
|
4668
4908
|
kind: operation.operation === "mutation" ? "mutation" : "query",
|
|
4669
4909
|
requires: parseRequires(operation),
|
|
4910
|
+
readBudget: parseReadBudget(operation),
|
|
4670
4911
|
variables,
|
|
4671
4912
|
definition: operation
|
|
4672
4913
|
};
|
|
@@ -5707,6 +5948,118 @@ function bindNamedParameters({ sql, names }) {
|
|
|
5707
5948
|
}
|
|
5708
5949
|
return result;
|
|
5709
5950
|
}
|
|
5951
|
+
var parser2;
|
|
5952
|
+
var functions = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
|
|
5953
|
+
var scalarTypes = /* @__PURE__ */ new Set([
|
|
5954
|
+
"bool",
|
|
5955
|
+
"boolean",
|
|
5956
|
+
"int2",
|
|
5957
|
+
"int4",
|
|
5958
|
+
"int8",
|
|
5959
|
+
"integer",
|
|
5960
|
+
"bigint",
|
|
5961
|
+
"smallint",
|
|
5962
|
+
"numeric",
|
|
5963
|
+
"decimal",
|
|
5964
|
+
"float4",
|
|
5965
|
+
"float8",
|
|
5966
|
+
"real",
|
|
5967
|
+
"text",
|
|
5968
|
+
"varchar",
|
|
5969
|
+
"bpchar",
|
|
5970
|
+
"uuid",
|
|
5971
|
+
"date",
|
|
5972
|
+
"timestamp",
|
|
5973
|
+
"timestamptz",
|
|
5974
|
+
"time",
|
|
5975
|
+
"timetz",
|
|
5976
|
+
"interval",
|
|
5977
|
+
"json",
|
|
5978
|
+
"jsonb",
|
|
5979
|
+
"bytea"
|
|
5980
|
+
]);
|
|
5981
|
+
var sqlValues = /* @__PURE__ */ new Set([
|
|
5982
|
+
"SVFOP_CURRENT_DATE",
|
|
5983
|
+
"SVFOP_CURRENT_TIME",
|
|
5984
|
+
"SVFOP_CURRENT_TIME_N",
|
|
5985
|
+
"SVFOP_CURRENT_TIMESTAMP",
|
|
5986
|
+
"SVFOP_CURRENT_TIMESTAMP_N",
|
|
5987
|
+
"SVFOP_LOCALTIME",
|
|
5988
|
+
"SVFOP_LOCALTIME_N",
|
|
5989
|
+
"SVFOP_LOCALTIMESTAMP",
|
|
5990
|
+
"SVFOP_LOCALTIMESTAMP_N",
|
|
5991
|
+
"SVFOP_CURRENT_ROLE",
|
|
5992
|
+
"SVFOP_CURRENT_USER",
|
|
5993
|
+
"SVFOP_USER",
|
|
5994
|
+
"SVFOP_SESSION_USER",
|
|
5995
|
+
"SVFOP_CURRENT_CATALOG",
|
|
5996
|
+
"SVFOP_CURRENT_SCHEMA"
|
|
5997
|
+
]);
|
|
5998
|
+
async function validateManagedViewQueries({ canonical }) {
|
|
5999
|
+
for (const definition of [canonical, ...canonical.imports ?? []]) {
|
|
6000
|
+
for (const view of definition.views ?? []) {
|
|
6001
|
+
const fail4 = (message) => {
|
|
6002
|
+
throw new OrmDefinitionError(`managed view '${definition.namespace}/${view.view}': ${message}`);
|
|
6003
|
+
};
|
|
6004
|
+
parser2 ??= new pgParser.PgParser({ version: 17 });
|
|
6005
|
+
const parsed = await parser2.parse(view.query);
|
|
6006
|
+
if (parsed.error || !parsed.tree) fail4("invalid SELECT query");
|
|
6007
|
+
const statements = list(obj(parsed.tree).stmts);
|
|
6008
|
+
if (statements.length !== 1 || !obj(obj(statements[0]).stmt).SelectStmt) fail4("one SELECT query is required");
|
|
6009
|
+
validateTree({ tree: parsed.tree, schema: ormSchemaName(definition.namespace), enums: definition.enums.map((entry) => entry.name), fail: fail4 });
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
}
|
|
6013
|
+
function pgCatalogName(parts) {
|
|
6014
|
+
if (parts.length === 1) return parts[0];
|
|
6015
|
+
if (parts.length === 2 && parts[0] === "pg_catalog") return parts[1];
|
|
6016
|
+
return void 0;
|
|
6017
|
+
}
|
|
6018
|
+
function reviewedFunctionName({ parts, format }) {
|
|
6019
|
+
return format === "COERCE_SQL_SYNTAX" && parts.length === 2 && parts[0] === "pg_catalog" && parts[1] === "position" ? "strpos" : pgCatalogName(parts);
|
|
6020
|
+
}
|
|
6021
|
+
function validateTree({ tree, ...scope }) {
|
|
6022
|
+
const pending = [tree];
|
|
6023
|
+
while (pending.length) {
|
|
6024
|
+
const value = pending.pop();
|
|
6025
|
+
if (Array.isArray(value)) {
|
|
6026
|
+
pending.push(...value);
|
|
6027
|
+
continue;
|
|
6028
|
+
}
|
|
6029
|
+
if (!value || typeof value !== "object") continue;
|
|
6030
|
+
const node = obj(value);
|
|
6031
|
+
for (const [kind, child] of Object.entries(node)) {
|
|
6032
|
+
validateNode({ kind, child, scope });
|
|
6033
|
+
pending.push(child);
|
|
6034
|
+
}
|
|
6035
|
+
}
|
|
6036
|
+
}
|
|
6037
|
+
function validateNode({ kind, child, scope }) {
|
|
6038
|
+
const { fail: fail4 } = scope;
|
|
6039
|
+
if (kind.endsWith("Stmt") && kind !== "SelectStmt") fail4("only SELECT statements are permitted");
|
|
6040
|
+
if (kind === "FuncCall") {
|
|
6041
|
+
const call = obj(child);
|
|
6042
|
+
const parts = strings(call.funcname);
|
|
6043
|
+
const name2 = reviewedFunctionName({ parts, format: call.funcformat });
|
|
6044
|
+
if (!name2 || !functions.has(name2)) fail4(`only explicitly allowlisted pg_catalog functions are permitted (found '${parts.join(".")}')`);
|
|
6045
|
+
}
|
|
6046
|
+
if (kind === "typeName" || kind === "TypeName") validateType({ node: obj(child), ...scope });
|
|
6047
|
+
if (kind === "A_Expr" && pgCatalogName(strings(obj(child).name)) === void 0) fail4("only pg_catalog operators are permitted");
|
|
6048
|
+
if (kind === "SortBy" && list(obj(child).useOp).length && pgCatalogName(strings(obj(child).useOp)) === void 0) fail4("only pg_catalog sort operators are permitted");
|
|
6049
|
+
if (kind === "SQLValueFunction" && !sqlValues.has(String(obj(child).op))) fail4("unsupported SQL value expression");
|
|
6050
|
+
if (kind === "ParamRef") fail4("view queries cannot contain parameters");
|
|
6051
|
+
if (kind === "A_Indirection" && list(obj(child).indirection).some((entry) => !obj(entry).A_Indices)) {
|
|
6052
|
+
fail4("composite field notation is not permitted in managed views");
|
|
6053
|
+
}
|
|
6054
|
+
}
|
|
6055
|
+
function validateType({ node, schema, enums, fail: fail4 }) {
|
|
6056
|
+
const parts = strings(node.names);
|
|
6057
|
+
const builtin = pgCatalogName(parts);
|
|
6058
|
+
const managedEnum = parts.length === 1 && enums.includes(parts[0]) || parts.length === 2 && parts[0] === schema && enums.includes(parts[1]);
|
|
6059
|
+
if (node.setof || node.pct_type || !managedEnum && (!builtin || !scalarTypes.has(builtin))) {
|
|
6060
|
+
fail4(`casts may only use supported pg_catalog scalar types or declared enums (found '${parts.join(".")}')`);
|
|
6061
|
+
}
|
|
6062
|
+
}
|
|
5710
6063
|
|
|
5711
6064
|
// src/sql/operations.ts
|
|
5712
6065
|
function isSqlOperationDocument(path, contents) {
|
|
@@ -5722,7 +6075,13 @@ function isSqlOperationDocument(path, contents) {
|
|
|
5722
6075
|
async function compileOperationDocuments(input) {
|
|
5723
6076
|
const sqlDocuments = input.documents.filter((d) => isSqlOperationDocument(d.path, d.contents));
|
|
5724
6077
|
const result = compileNamespace({ ...input, documents: input.documents.filter((d) => !isSqlOperationDocument(d.path, d.contents)) });
|
|
5725
|
-
if (!result.ok
|
|
6078
|
+
if (!result.ok) return result;
|
|
6079
|
+
const viewDiagnostics = await managedViewDiagnostics(input.canonical);
|
|
6080
|
+
if (viewDiagnostics.length) return { ok: false, diagnostics: [...result.diagnostics, ...viewDiagnostics] };
|
|
6081
|
+
if (!sqlDocuments.length) return result;
|
|
6082
|
+
return compileSqlDocuments({ input, result, sqlDocuments });
|
|
6083
|
+
}
|
|
6084
|
+
async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
5726
6085
|
for (const document of sqlDocuments) {
|
|
5727
6086
|
try {
|
|
5728
6087
|
const definition = defineOperation(JSON.parse(document.contents));
|
|
@@ -5816,6 +6175,18 @@ async function compileOperationDocuments(input) {
|
|
|
5816
6175
|
result.manifest.opsHash = result.artifact.opsHash;
|
|
5817
6176
|
return { ...result, artifact: normalizeIr(result.artifact) };
|
|
5818
6177
|
}
|
|
6178
|
+
async function managedViewDiagnostics(canonical) {
|
|
6179
|
+
try {
|
|
6180
|
+
await validateManagedViewQueries({ canonical });
|
|
6181
|
+
return [];
|
|
6182
|
+
} catch (error) {
|
|
6183
|
+
return [{
|
|
6184
|
+
severity: "error",
|
|
6185
|
+
code: "invalid-view-query",
|
|
6186
|
+
message: error instanceof Error ? error.message : "invalid managed view query"
|
|
6187
|
+
}];
|
|
6188
|
+
}
|
|
6189
|
+
}
|
|
5819
6190
|
var parameterName = zod.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(63).refine((value) => !["__proto__", "prototype", "constructor"].includes(value), "reserved parameter name");
|
|
5820
6191
|
var parameter = zod.z.discriminatedUnion("type", [
|
|
5821
6192
|
zod.z.object({ type: zod.z.literal("text"), value: zod.z.string().nullable() }).strict(),
|
|
@@ -5955,6 +6326,7 @@ exports.typeNameForTable = typeNameForTable;
|
|
|
5955
6326
|
exports.validateAppendInput = validateAppendInput;
|
|
5956
6327
|
exports.validateBoolExpInput = validateBoolExpInput;
|
|
5957
6328
|
exports.validateInsertInput = validateInsertInput;
|
|
6329
|
+
exports.validateManagedViewQueries = validateManagedViewQueries;
|
|
5958
6330
|
exports.validateOnConflictInput = validateOnConflictInput;
|
|
5959
6331
|
exports.validateOrderByInput = validateOrderByInput;
|
|
5960
6332
|
exports.validateSelectColumns = validateSelectColumns;
|