@sequenceholdings/orm 2.2.0 → 2.4.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.
@@ -2,7 +2,7 @@ import { CanonicalEnum, CanonicalDefinition } from './canonical.cjs';
2
2
  import { ClaimKey, PredIr, RootIr, InputRefKind, TableMetaIr, CompiledOperation, CompiledArtifact, VariableIr, OrderIr, SelectionIr } from './ir.cjs';
3
3
  import { DocumentNode, OperationDefinitionNode, FragmentDefinitionNode, GraphQLSchema, ValueNode, SelectionSetNode } from 'graphql';
4
4
  export { DynamicInputIssue, DynamicInputResult, NormalizedBoolExp, NormalizedOnConflict, NormalizedOrderBy, NormalizedRow, ORDER_BY_DIRECTIONS, ScalarKind, allowedBoolExpOperators, allowedOps, validateAppendInput, validateBoolExpInput, validateInsertInput, validateOnConflictInput, validateOrderByInput, validateSelectColumns, validateSetInput } from './dynamic-input.cjs';
5
- export { CompilePolicyInput, CompilePredicateInput, POLICY_COMPARE_OPS, POLICY_MAX_LINK_DEPTH, ParsedPolicy, PolicyColumnInfo, PolicyCompareOp, PolicyCompileError, PolicyCompileErrorCode, PolicyLinkInfo, PolicyNode, PolicyOperand, PolicyParseError, PolicyResolver, PolicyTableInfo, assertClaimsOnlyWhen, compilePolicy, compilePredicateV2, parsePolicy, walkPolicy } from './predicate-v2.cjs';
5
+ export { CompilePolicyInput, CompilePredicateInput, POLICY_COMPARE_OPS, POLICY_MAX_LINK_DEPTH, ParsedPolicy, PolicyAccess, PolicyColumnInfo, PolicyCompareOp, PolicyCompileError, PolicyCompileErrorCode, PolicyLinkInfo, PolicyNode, PolicyOperand, PolicyParseError, PolicyResolver, PolicyTableInfo, assertClaimsOnlyWhen, compilePolicy, compilePredicateV2, parsePolicy, walkPolicy } from './predicate-v2.cjs';
6
6
  import { z } from 'zod';
7
7
  export { P as PLATFORM_USER_TYPENAME, c as camelCase, g as enumTypeName, p as pascalCase } from './naming-CU9ThP_E.cjs';
8
8
  export { S as SqlOperationDefinition, d as defineOperation, s as sqlOperationDefinitionSchema } from './definition-DjfC7Lll.cjs';
@@ -50,9 +50,11 @@ declare class NamespaceCompileError extends Error {
50
50
  * - document-authored relationship predicates (exists/aggPredicate) get the
51
51
  * TARGET table's read policy injected too, so filtering through a link
52
52
  * can't become an existence oracle on rows the caller can't read;
53
- * - write policy AND-ed into update/delete row filters, and set as the
54
- * insert check for inserts (claims + inserted-values only in v2.0 — an
55
- * exists() write policy makes a table un-insertable, loudly);
53
+ * - write policy AND-ed into update/delete row filters, set as the insert
54
+ * check for inserts, and re-checked against the NEW rows of every update.
55
+ * Link-traversing (exists/visible) write policies are evaluated by the data
56
+ * plane against the proposed rows in the same statement, so "only on a
57
+ * parent the caller can see" holds for inserts and FK-changing updates;
56
58
  * - `col = claim(k)` AND-conjuncts of the write policy become pinned columns
57
59
  * merged into every inserted row (and excluded from insertable/settable).
58
60
  */
@@ -2,7 +2,7 @@ import { CanonicalEnum, CanonicalDefinition } from './canonical.js';
2
2
  import { ClaimKey, PredIr, RootIr, InputRefKind, TableMetaIr, CompiledOperation, CompiledArtifact, VariableIr, OrderIr, SelectionIr } from './ir.js';
3
3
  import { DocumentNode, OperationDefinitionNode, FragmentDefinitionNode, GraphQLSchema, ValueNode, SelectionSetNode } from 'graphql';
4
4
  export { DynamicInputIssue, DynamicInputResult, NormalizedBoolExp, NormalizedOnConflict, NormalizedOrderBy, NormalizedRow, ORDER_BY_DIRECTIONS, ScalarKind, allowedBoolExpOperators, allowedOps, validateAppendInput, validateBoolExpInput, validateInsertInput, validateOnConflictInput, validateOrderByInput, validateSelectColumns, validateSetInput } from './dynamic-input.js';
5
- export { CompilePolicyInput, CompilePredicateInput, POLICY_COMPARE_OPS, POLICY_MAX_LINK_DEPTH, ParsedPolicy, PolicyColumnInfo, PolicyCompareOp, PolicyCompileError, PolicyCompileErrorCode, PolicyLinkInfo, PolicyNode, PolicyOperand, PolicyParseError, PolicyResolver, PolicyTableInfo, assertClaimsOnlyWhen, compilePolicy, compilePredicateV2, parsePolicy, walkPolicy } from './predicate-v2.js';
5
+ export { CompilePolicyInput, CompilePredicateInput, POLICY_COMPARE_OPS, POLICY_MAX_LINK_DEPTH, ParsedPolicy, PolicyAccess, PolicyColumnInfo, PolicyCompareOp, PolicyCompileError, PolicyCompileErrorCode, PolicyLinkInfo, PolicyNode, PolicyOperand, PolicyParseError, PolicyResolver, PolicyTableInfo, assertClaimsOnlyWhen, compilePolicy, compilePredicateV2, parsePolicy, walkPolicy } from './predicate-v2.js';
6
6
  import { z } from 'zod';
7
7
  export { P as PLATFORM_USER_TYPENAME, c as camelCase, g as enumTypeName, p as pascalCase } from './naming-CPfx7EfF.js';
8
8
  export { S as SqlOperationDefinition, d as defineOperation, s as sqlOperationDefinitionSchema } from './definition-DjfC7Lll.js';
@@ -50,9 +50,11 @@ declare class NamespaceCompileError extends Error {
50
50
  * - document-authored relationship predicates (exists/aggPredicate) get the
51
51
  * TARGET table's read policy injected too, so filtering through a link
52
52
  * can't become an existence oracle on rows the caller can't read;
53
- * - write policy AND-ed into update/delete row filters, and set as the
54
- * insert check for inserts (claims + inserted-values only in v2.0 — an
55
- * exists() write policy makes a table un-insertable, loudly);
53
+ * - write policy AND-ed into update/delete row filters, set as the insert
54
+ * check for inserts, and re-checked against the NEW rows of every update.
55
+ * Link-traversing (exists/visible) write policies are evaluated by the data
56
+ * plane against the proposed rows in the same statement, so "only on a
57
+ * parent the caller can see" holds for inserts and FK-changing updates;
56
58
  * - `col = claim(k)` AND-conjuncts of the write policy become pinned columns
57
59
  * merged into every inserted row (and excluded from insertable/settable).
58
60
  */
package/dist/compiler.js CHANGED
@@ -1121,14 +1121,20 @@ function requiredArtifactIrVersion(operations) {
1121
1121
  var CLAIMS_CONTRACT = {
1122
1122
  sub: "string",
1123
1123
  org: "string?",
1124
+ ext_subject: "string?",
1125
+ ext_email: "string?",
1124
1126
  roles: "string[]",
1125
1127
  capabilities: "string[]"
1126
1128
  };
1129
+ var EXTERNAL_POLICY_CLAIM_KEYS = ["ext_subject", "ext_email"];
1130
+ function isExternalPolicyClaimKey(key) {
1131
+ return EXTERNAL_POLICY_CLAIM_KEYS.includes(key);
1132
+ }
1127
1133
  var scalarLiteral = z.union([z.string(), z.number(), z.boolean(), z.null()]);
1128
1134
  var valueIrSchema = z.discriminatedUnion("kind", [
1129
1135
  z.object({ kind: z.literal("literal"), value: z.union([scalarLiteral, z.array(scalarLiteral)]) }),
1130
1136
  z.object({ kind: z.literal("variable"), name: z.string() }),
1131
- z.object({ kind: z.literal("claim"), key: z.enum(["sub", "org"]) })
1137
+ z.object({ kind: z.literal("claim"), key: z.enum(["sub", "org", ...EXTERNAL_POLICY_CLAIM_KEYS]) })
1132
1138
  ]);
1133
1139
  var CMP_OPS = [
1134
1140
  "eq",
@@ -1848,6 +1854,7 @@ var Parser2 = class {
1848
1854
  return node;
1849
1855
  }
1850
1856
  if (this.peekKeyword("exists")) return this.parseExists();
1857
+ if (this.peekKeyword("visible") && this.tokens[this.index + 1]?.kind === "lparen") return this.parseVisible();
1851
1858
  return this.parseComparison();
1852
1859
  }
1853
1860
  parseBooleanCall(fn) {
@@ -1867,6 +1874,13 @@ var Parser2 = class {
1867
1874
  this.expect("rparen", "')' closing exists(...)");
1868
1875
  return { kind: "exists", path, where };
1869
1876
  }
1877
+ parseVisible() {
1878
+ this.advance();
1879
+ this.expect("lparen", "'(' after visible");
1880
+ const path = this.parsePath("a link path in visible(...)");
1881
+ this.expect("rparen", "')' closing visible(...)");
1882
+ return { kind: "visible", path };
1883
+ }
1870
1884
  parseComparison() {
1871
1885
  const left = this.parseOperand();
1872
1886
  const operator = this.expect("operator", "a comparison operator");
@@ -1982,6 +1996,24 @@ function parsePolicy(source) {
1982
1996
 
1983
1997
  // src/predicate-v2/compile.ts
1984
1998
  var POLICY_MAX_LINK_DEPTH = 3;
1999
+ function scopeToNamespace(pred, namespace) {
2000
+ switch (pred.kind) {
2001
+ case "capability":
2002
+ case "role":
2003
+ return pred.namespace ? pred : { ...pred, namespace };
2004
+ case "and":
2005
+ case "or":
2006
+ return { ...pred, operands: pred.operands.map((operand) => scopeToNamespace(operand, namespace)) };
2007
+ case "not":
2008
+ return { ...pred, operand: scopeToNamespace(pred.operand, namespace) };
2009
+ case "exists":
2010
+ return { ...pred, where: scopeToNamespace(pred.where, namespace) };
2011
+ case "aggPredicate":
2012
+ return pred.where ? { ...pred, where: scopeToNamespace(pred.where, namespace) } : pred;
2013
+ default:
2014
+ return pred;
2015
+ }
2016
+ }
1985
2017
  var CMP_BY_OP = {
1986
2018
  "=": "eq",
1987
2019
  "!=": "neq",
@@ -2015,7 +2047,7 @@ function resolveLink(info, ref) {
2015
2047
  function columnCandidates(info) {
2016
2048
  return [...info.columns.map((c) => c.name), ...info.links.map((l) => l.name)];
2017
2049
  }
2018
- var PolicyCompiler = class {
2050
+ var PolicyCompiler = class _PolicyCompiler {
2019
2051
  constructor(ctx) {
2020
2052
  this.ctx = ctx;
2021
2053
  }
@@ -2027,13 +2059,16 @@ var PolicyCompiler = class {
2027
2059
  case "or":
2028
2060
  return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
2029
2061
  case "not":
2030
- return { kind: "not", operand: this.compile(node.operand, table, depth) };
2062
+ assertNoNegatedExternalClaim(node.operand);
2063
+ return { kind: "not", operand: new _PolicyCompiler({ ...this.ctx, negated: true }).compile(node.operand, table, depth) };
2031
2064
  case "capability":
2032
2065
  return { kind: "capability", key: node.key };
2033
2066
  case "role":
2034
2067
  return { kind: "role", key: node.key };
2035
2068
  case "exists":
2036
2069
  return this.compileExists(node.path, node.where, table, depth);
2070
+ case "visible":
2071
+ return this.compileVisible(node.path, table, depth);
2037
2072
  case "compare":
2038
2073
  return this.compileCompare(node, table, depth);
2039
2074
  }
@@ -2047,6 +2082,36 @@ var PolicyCompiler = class {
2047
2082
  }
2048
2083
  return pred;
2049
2084
  }
2085
+ /** `visible(path)` → `exists(path, <final target's read policy>)`. */
2086
+ compileVisible(path, table, depth) {
2087
+ const { links, finalTable } = this.resolveLinkPath(path, table, depth, { toOneOnly: false });
2088
+ const readPolicy = this.ctx.resolver.readPolicy;
2089
+ if (!readPolicy) {
2090
+ throw new PolicyCompileError(
2091
+ `visible(${path.join(".")}) needs the target's read policy, which this compile context does not provide`,
2092
+ "visible-unresolvable"
2093
+ );
2094
+ }
2095
+ if (this.ctx.visiting.includes(finalTable)) {
2096
+ throw new PolicyCompileError(
2097
+ `visible(${path.join(".")}) on '${table}' reaches '${finalTable}' whose read policy is already being expanded (${[...this.ctx.visiting, finalTable].join(" \u2192 ")})`,
2098
+ "visible-cycle"
2099
+ );
2100
+ }
2101
+ const expression = readPolicy(finalTable);
2102
+ let pred = expression === void 0 ? { kind: "const", value: true } : compilePolicyAt({
2103
+ expression,
2104
+ table: finalTable,
2105
+ ctx: { ...this.ctx, visiting: [...this.ctx.visiting, finalTable] },
2106
+ depth: depth + links.length
2107
+ });
2108
+ const owner = this.ctx.resolver.policyNamespace?.(finalTable);
2109
+ if (owner) pred = scopeToNamespace(pred, owner);
2110
+ for (let i = links.length - 1; i >= 0; i -= 1) {
2111
+ pred = { kind: "exists", link: links[i].name, where: pred };
2112
+ }
2113
+ return pred;
2114
+ }
2050
2115
  resolveLinkPath(path, table, depth, { toOneOnly }) {
2051
2116
  const links = [];
2052
2117
  let current = table;
@@ -2106,6 +2171,10 @@ var PolicyCompiler = class {
2106
2171
  columnCandidates(info)
2107
2172
  );
2108
2173
  }
2174
+ if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
2175
+ if (this.ctx.negated) throw negatedExternalClaim(valueSide.key);
2176
+ assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
2177
+ }
2109
2178
  let cmp = this.buildCmp(op, column, valueSide);
2110
2179
  for (let i = links.length - 1; i >= 0; i -= 1) {
2111
2180
  cmp = { kind: "exists", link: links[i].name, where: cmp };
@@ -2126,10 +2195,45 @@ var PolicyCompiler = class {
2126
2195
  return { kind: "cmp", op: CMP_BY_OP[op], column, value };
2127
2196
  }
2128
2197
  };
2198
+ function assertExternalClaimComparison({ op, key, column }) {
2199
+ if (op !== "=") {
2200
+ throw new PolicyCompileError(
2201
+ `claim('${key}') supports = comparisons only, got '${op}'`,
2202
+ "unsupported-comparison"
2203
+ );
2204
+ }
2205
+ if (column?.type !== "text") {
2206
+ const actual = column?.type === void 0 ? "of unknown type" : typeof column.type === "string" ? column.type : "an enum";
2207
+ throw new PolicyCompileError(
2208
+ `claim('${key}') is text and compares to a text column only \u2014 '${column?.name ?? "?"}' is ${actual}`,
2209
+ "claim-type-mismatch"
2210
+ );
2211
+ }
2212
+ }
2213
+ function negatedExternalClaim(key) {
2214
+ return new PolicyCompileError(
2215
+ `claim('${key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
2216
+ "unsupported-comparison"
2217
+ );
2218
+ }
2219
+ function assertNoNegatedExternalClaim(operand) {
2220
+ walkPolicy(operand, (node) => {
2221
+ if (node.kind !== "compare") return;
2222
+ const claim = [node.left, node.right].find((side) => side.kind === "claim" && isExternalPolicyClaimKey(side.key));
2223
+ if (claim?.kind === "claim") throw negatedExternalClaim(claim.key);
2224
+ });
2225
+ }
2129
2226
  function compilePredicateV2({ expression, table, resolver, maxLinkDepth }) {
2227
+ return compilePredicateAt({
2228
+ expression,
2229
+ table,
2230
+ depth: 0,
2231
+ ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: [table] }
2232
+ });
2233
+ }
2234
+ function compilePredicateAt({ expression, table, ctx, depth }) {
2130
2235
  const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
2131
- const compiler = new PolicyCompiler({ resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH });
2132
- return compiler.compile(parsed.ast, table, 0);
2236
+ return new PolicyCompiler(ctx).compile(parsed.ast, table, depth);
2133
2237
  }
2134
2238
  function assertClaimsOnlyWhen(parsed) {
2135
2239
  walkPolicy(parsed.ast, (node) => {
@@ -2146,25 +2250,34 @@ function assertClaimsOnlyWhen(parsed) {
2146
2250
  "when-not-claims-only"
2147
2251
  );
2148
2252
  }
2149
- if (node.kind === "exists") {
2253
+ if (node.kind === "exists" || node.kind === "visible") {
2150
2254
  throw new PolicyCompileError(
2151
- "variant 'when' guards are claims-only \u2014 exists() is not allowed",
2255
+ `variant 'when' guards are claims-only \u2014 ${node.kind}() is not allowed`,
2152
2256
  "when-not-claims-only"
2153
2257
  );
2154
2258
  }
2155
2259
  });
2156
2260
  }
2157
- function compilePolicy({ expression, table, resolver, maxLinkDepth }) {
2158
- if (typeof expression === "string") {
2159
- return compilePredicateV2({ expression, table, resolver, maxLinkDepth });
2160
- }
2161
- const defaultPred = compilePredicateV2({ expression: expression.default, table, resolver, maxLinkDepth });
2261
+ function initialVisiting({ table, access }) {
2262
+ return (access ?? "read") === "read" ? [table] : [];
2263
+ }
2264
+ function compilePolicy({ expression, table, resolver, maxLinkDepth, access }) {
2265
+ return compilePolicyAt({
2266
+ expression,
2267
+ table,
2268
+ depth: 0,
2269
+ ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: initialVisiting({ table, access }) }
2270
+ });
2271
+ }
2272
+ function compilePolicyAt({ expression, table, ctx, depth }) {
2273
+ if (typeof expression === "string") return compilePredicateAt({ expression, table, ctx, depth });
2274
+ const defaultPred = compilePredicateAt({ expression: expression.default, table, ctx, depth });
2162
2275
  if (expression.variants.length === 0) return defaultPred;
2163
2276
  const branches = expression.variants.map((variant) => {
2164
2277
  const parsedWhen = parsePolicy(variant.when);
2165
2278
  assertClaimsOnlyWhen(parsedWhen);
2166
- const when = compilePredicateV2({ expression: parsedWhen, table, resolver, maxLinkDepth });
2167
- const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateV2({ expression: variant.policy, table, resolver, maxLinkDepth });
2279
+ const when = compilePredicateAt({ expression: parsedWhen, table, ctx, depth });
2280
+ const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateAt({ expression: variant.policy, table, ctx, depth });
2168
2281
  return { kind: "and", operands: [when, grant] };
2169
2282
  });
2170
2283
  return { kind: "or", operands: [defaultPred, ...branches] };
@@ -2380,12 +2493,6 @@ var PolicyInjector = class {
2380
2493
  if (root.op === "resolvedWrite") return this.injectResolvedWrite(root, returning);
2381
2494
  if (root.op === "insert" || root.op === "insertOne") {
2382
2495
  const insertPolicy = this.insert(table);
2383
- if (predContainsExists(insertPolicy)) {
2384
- throw new DocumentCompileError(
2385
- `table '${table}' cannot be inserted into: v2.0 insert checks are claims + inserted-values only, but its insert policy traverses a link (exists)`,
2386
- "insert-check-unrepresentable"
2387
- );
2388
- }
2389
2496
  const pins = (this.ctx.pins.get(table) ?? []).map(
2390
2497
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2391
2498
  );
@@ -2400,22 +2507,14 @@ var PolicyInjector = class {
2400
2507
  const updatePolicy = this.update(table);
2401
2508
  const where = andPred(updatePolicy, root.where ? this.injectDocPred(root.where, table) : void 0);
2402
2509
  if (root.op === "update" || root.op === "updateByPk") {
2403
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2404
- return { ...root, where, ...check, returning };
2510
+ return { ...root, where, insertCheck: updatePolicy, returning };
2405
2511
  }
2406
2512
  if (root.op === "cowOverride") {
2407
2513
  const insertPolicy = this.insert(table);
2408
- if (predContainsExists(insertPolicy)) {
2409
- throw new DocumentCompileError(
2410
- `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)`,
2411
- "insert-check-unrepresentable"
2412
- );
2413
- }
2414
2514
  const pins = (this.ctx.pins.get(table) ?? []).map(
2415
2515
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2416
2516
  );
2417
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2418
- return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, ...check, returning };
2517
+ return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, insertCheck: updatePolicy, returning };
2419
2518
  }
2420
2519
  return { ...root, where, returning };
2421
2520
  }
@@ -2637,7 +2736,7 @@ function scopePolicy(pred, namespace) {
2637
2736
  switch (pred.kind) {
2638
2737
  case "capability":
2639
2738
  case "role":
2640
- return { ...pred, namespace };
2739
+ return pred.namespace ? pred : { ...pred, namespace };
2641
2740
  case "and":
2642
2741
  case "or":
2643
2742
  return { ...pred, operands: pred.operands.map((p) => scopePolicy(p, namespace)) };
@@ -2846,7 +2945,14 @@ function buildCompileContext(rawCanonical) {
2846
2945
  const updatePolicies = /* @__PURE__ */ new Map();
2847
2946
  const pins = /* @__PURE__ */ new Map();
2848
2947
  const splitWritePolicyTables = /* @__PURE__ */ new Set();
2849
- const resolver = { tables };
2948
+ const readPolicyExpressions = new Map(relations.map((relation) => [relation.table, relation.policies.read]));
2949
+ const resolver = {
2950
+ tables,
2951
+ readPolicy: (table) => readPolicyExpressions.get(table),
2952
+ // In a composed namespace, an imported table's read policy (inlined by
2953
+ // visible()) is checked against the namespace that owns it.
2954
+ ...normalized.imports?.length ? { policyNamespace: (table) => owners.get(table)?.namespace } : {}
2955
+ };
2850
2956
  for (const table of relations) {
2851
2957
  const isView = viewNames.has(table.table);
2852
2958
  const compilePolicyFor = (access, store) => {
@@ -2855,7 +2961,7 @@ function buildCompileContext(rawCanonical) {
2855
2961
  return;
2856
2962
  }
2857
2963
  try {
2858
- const pred = compilePolicy({ expression, table: table.table, resolver });
2964
+ const pred = compilePolicy({ expression, table: table.table, resolver, access });
2859
2965
  store.set(table.table, normalized.imports?.length ? scopePolicy(pred, owners.get(table.table).namespace) : pred);
2860
2966
  } catch (error) {
2861
2967
  if (error instanceof PolicyCompileError || error instanceof PolicyParseError) {