@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.
Files changed (59) hide show
  1. package/dist/authoring.cjs +53 -7
  2. package/dist/authoring.cjs.map +1 -1
  3. package/dist/authoring.d.cts +11 -7
  4. package/dist/authoring.d.ts +11 -7
  5. package/dist/authoring.js +53 -7
  6. package/dist/authoring.js.map +1 -1
  7. package/dist/canonical.cjs +48 -2
  8. package/dist/canonical.cjs.map +1 -1
  9. package/dist/canonical.d.cts +72 -72
  10. package/dist/canonical.d.ts +72 -72
  11. package/dist/canonical.js +48 -2
  12. package/dist/canonical.js.map +1 -1
  13. package/dist/cli.cjs +237 -20
  14. package/dist/cli.cjs.map +1 -1
  15. package/dist/cli.js +237 -20
  16. package/dist/cli.js.map +1 -1
  17. package/dist/client.cjs +1 -1
  18. package/dist/client.cjs.map +1 -1
  19. package/dist/client.d.cts +1 -1
  20. package/dist/client.d.ts +1 -1
  21. package/dist/client.js +1 -1
  22. package/dist/client.js.map +1 -1
  23. package/dist/compiler.cjs +386 -14
  24. package/dist/compiler.cjs.map +1 -1
  25. package/dist/compiler.d.cts +16 -3
  26. package/dist/compiler.d.ts +16 -3
  27. package/dist/compiler.js +386 -15
  28. package/dist/compiler.js.map +1 -1
  29. package/dist/{constants-C6EG6Wq7.d.cts → constants-Dou1RkTo.d.cts} +7 -2
  30. package/dist/{constants-C6EG6Wq7.d.ts → constants-Dou1RkTo.d.ts} +7 -2
  31. package/dist/dynamic-input.d.cts +1 -1
  32. package/dist/dynamic-input.d.ts +1 -1
  33. package/dist/index.cjs +329 -130
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +66 -58
  36. package/dist/index.d.ts +66 -58
  37. package/dist/index.js +329 -131
  38. package/dist/index.js.map +1 -1
  39. package/dist/ir.cjs +50 -3
  40. package/dist/ir.cjs.map +1 -1
  41. package/dist/ir.d.cts +71 -71
  42. package/dist/ir.d.ts +71 -71
  43. package/dist/ir.js +50 -3
  44. package/dist/ir.js.map +1 -1
  45. package/dist/predicate-v2.cjs +50 -3
  46. package/dist/predicate-v2.cjs.map +1 -1
  47. package/dist/predicate-v2.d.cts +1 -1
  48. package/dist/predicate-v2.d.ts +1 -1
  49. package/dist/predicate-v2.js +50 -3
  50. package/dist/predicate-v2.js.map +1 -1
  51. package/dist/sdl.cjs +53 -3
  52. package/dist/sdl.cjs.map +1 -1
  53. package/dist/sdl.d.cts +3 -1
  54. package/dist/sdl.d.ts +3 -1
  55. package/dist/sdl.js +53 -3
  56. package/dist/sdl.js.map +1 -1
  57. package/package.json +1 -1
  58. package/dist/{definition-DjfC7Lll.d.cts → definition-B99BsOAO.d.cts} +4 -4
  59. 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) => `${ORM_SCHEMA_PREFIX}${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).min(1).max(8).optional(),
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) : isView ? [] : active.map((c) => c.name),
2896
- settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey.includes(name2)) : isView ? [] : active.filter((c) => !c.onUpdateFn).map((c) => c.name),
2897
- appendable: isView ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
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: MAX_LIMIT };
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, MAX_LIMIT) };
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
- return lowerer.lowerRoots(restricted.definition, restricted.kind);
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 || !sqlDocuments.length) return result;
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;