@sequenceholdings/orm 2.3.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.
package/dist/compiler.cjs CHANGED
@@ -1856,6 +1856,7 @@ var Parser2 = class {
1856
1856
  return node;
1857
1857
  }
1858
1858
  if (this.peekKeyword("exists")) return this.parseExists();
1859
+ if (this.peekKeyword("visible") && this.tokens[this.index + 1]?.kind === "lparen") return this.parseVisible();
1859
1860
  return this.parseComparison();
1860
1861
  }
1861
1862
  parseBooleanCall(fn) {
@@ -1875,6 +1876,13 @@ var Parser2 = class {
1875
1876
  this.expect("rparen", "')' closing exists(...)");
1876
1877
  return { kind: "exists", path, where };
1877
1878
  }
1879
+ parseVisible() {
1880
+ this.advance();
1881
+ this.expect("lparen", "'(' after visible");
1882
+ const path = this.parsePath("a link path in visible(...)");
1883
+ this.expect("rparen", "')' closing visible(...)");
1884
+ return { kind: "visible", path };
1885
+ }
1878
1886
  parseComparison() {
1879
1887
  const left = this.parseOperand();
1880
1888
  const operator = this.expect("operator", "a comparison operator");
@@ -1990,6 +1998,24 @@ function parsePolicy(source) {
1990
1998
 
1991
1999
  // src/predicate-v2/compile.ts
1992
2000
  var POLICY_MAX_LINK_DEPTH = 3;
2001
+ function scopeToNamespace(pred, namespace) {
2002
+ switch (pred.kind) {
2003
+ case "capability":
2004
+ case "role":
2005
+ return pred.namespace ? pred : { ...pred, namespace };
2006
+ case "and":
2007
+ case "or":
2008
+ return { ...pred, operands: pred.operands.map((operand) => scopeToNamespace(operand, namespace)) };
2009
+ case "not":
2010
+ return { ...pred, operand: scopeToNamespace(pred.operand, namespace) };
2011
+ case "exists":
2012
+ return { ...pred, where: scopeToNamespace(pred.where, namespace) };
2013
+ case "aggPredicate":
2014
+ return pred.where ? { ...pred, where: scopeToNamespace(pred.where, namespace) } : pred;
2015
+ default:
2016
+ return pred;
2017
+ }
2018
+ }
1993
2019
  var CMP_BY_OP = {
1994
2020
  "=": "eq",
1995
2021
  "!=": "neq",
@@ -2023,7 +2049,7 @@ function resolveLink(info, ref) {
2023
2049
  function columnCandidates(info) {
2024
2050
  return [...info.columns.map((c) => c.name), ...info.links.map((l) => l.name)];
2025
2051
  }
2026
- var PolicyCompiler = class {
2052
+ var PolicyCompiler = class _PolicyCompiler {
2027
2053
  constructor(ctx) {
2028
2054
  this.ctx = ctx;
2029
2055
  }
@@ -2036,13 +2062,15 @@ var PolicyCompiler = class {
2036
2062
  return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
2037
2063
  case "not":
2038
2064
  assertNoNegatedExternalClaim(node.operand);
2039
- return { kind: "not", operand: this.compile(node.operand, table, depth) };
2065
+ return { kind: "not", operand: new _PolicyCompiler({ ...this.ctx, negated: true }).compile(node.operand, table, depth) };
2040
2066
  case "capability":
2041
2067
  return { kind: "capability", key: node.key };
2042
2068
  case "role":
2043
2069
  return { kind: "role", key: node.key };
2044
2070
  case "exists":
2045
2071
  return this.compileExists(node.path, node.where, table, depth);
2072
+ case "visible":
2073
+ return this.compileVisible(node.path, table, depth);
2046
2074
  case "compare":
2047
2075
  return this.compileCompare(node, table, depth);
2048
2076
  }
@@ -2056,6 +2084,36 @@ var PolicyCompiler = class {
2056
2084
  }
2057
2085
  return pred;
2058
2086
  }
2087
+ /** `visible(path)` → `exists(path, <final target's read policy>)`. */
2088
+ compileVisible(path, table, depth) {
2089
+ const { links, finalTable } = this.resolveLinkPath(path, table, depth, { toOneOnly: false });
2090
+ const readPolicy = this.ctx.resolver.readPolicy;
2091
+ if (!readPolicy) {
2092
+ throw new PolicyCompileError(
2093
+ `visible(${path.join(".")}) needs the target's read policy, which this compile context does not provide`,
2094
+ "visible-unresolvable"
2095
+ );
2096
+ }
2097
+ if (this.ctx.visiting.includes(finalTable)) {
2098
+ throw new PolicyCompileError(
2099
+ `visible(${path.join(".")}) on '${table}' reaches '${finalTable}' whose read policy is already being expanded (${[...this.ctx.visiting, finalTable].join(" \u2192 ")})`,
2100
+ "visible-cycle"
2101
+ );
2102
+ }
2103
+ const expression = readPolicy(finalTable);
2104
+ let pred = expression === void 0 ? { kind: "const", value: true } : compilePolicyAt({
2105
+ expression,
2106
+ table: finalTable,
2107
+ ctx: { ...this.ctx, visiting: [...this.ctx.visiting, finalTable] },
2108
+ depth: depth + links.length
2109
+ });
2110
+ const owner = this.ctx.resolver.policyNamespace?.(finalTable);
2111
+ if (owner) pred = scopeToNamespace(pred, owner);
2112
+ for (let i = links.length - 1; i >= 0; i -= 1) {
2113
+ pred = { kind: "exists", link: links[i].name, where: pred };
2114
+ }
2115
+ return pred;
2116
+ }
2059
2117
  resolveLinkPath(path, table, depth, { toOneOnly }) {
2060
2118
  const links = [];
2061
2119
  let current = table;
@@ -2116,6 +2174,7 @@ var PolicyCompiler = class {
2116
2174
  );
2117
2175
  }
2118
2176
  if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
2177
+ if (this.ctx.negated) throw negatedExternalClaim(valueSide.key);
2119
2178
  assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
2120
2179
  }
2121
2180
  let cmp = this.buildCmp(op, column, valueSide);
@@ -2153,22 +2212,30 @@ function assertExternalClaimComparison({ op, key, column }) {
2153
2212
  );
2154
2213
  }
2155
2214
  }
2215
+ function negatedExternalClaim(key) {
2216
+ return new PolicyCompileError(
2217
+ `claim('${key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
2218
+ "unsupported-comparison"
2219
+ );
2220
+ }
2156
2221
  function assertNoNegatedExternalClaim(operand) {
2157
2222
  walkPolicy(operand, (node) => {
2158
2223
  if (node.kind !== "compare") return;
2159
2224
  const claim = [node.left, node.right].find((side) => side.kind === "claim" && isExternalPolicyClaimKey(side.key));
2160
- if (claim?.kind === "claim") {
2161
- throw new PolicyCompileError(
2162
- `claim('${claim.key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
2163
- "unsupported-comparison"
2164
- );
2165
- }
2225
+ if (claim?.kind === "claim") throw negatedExternalClaim(claim.key);
2166
2226
  });
2167
2227
  }
2168
2228
  function compilePredicateV2({ expression, table, resolver, maxLinkDepth }) {
2229
+ return compilePredicateAt({
2230
+ expression,
2231
+ table,
2232
+ depth: 0,
2233
+ ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: [table] }
2234
+ });
2235
+ }
2236
+ function compilePredicateAt({ expression, table, ctx, depth }) {
2169
2237
  const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
2170
- const compiler = new PolicyCompiler({ resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH });
2171
- return compiler.compile(parsed.ast, table, 0);
2238
+ return new PolicyCompiler(ctx).compile(parsed.ast, table, depth);
2172
2239
  }
2173
2240
  function assertClaimsOnlyWhen(parsed) {
2174
2241
  walkPolicy(parsed.ast, (node) => {
@@ -2185,25 +2252,34 @@ function assertClaimsOnlyWhen(parsed) {
2185
2252
  "when-not-claims-only"
2186
2253
  );
2187
2254
  }
2188
- if (node.kind === "exists") {
2255
+ if (node.kind === "exists" || node.kind === "visible") {
2189
2256
  throw new PolicyCompileError(
2190
- "variant 'when' guards are claims-only \u2014 exists() is not allowed",
2257
+ `variant 'when' guards are claims-only \u2014 ${node.kind}() is not allowed`,
2191
2258
  "when-not-claims-only"
2192
2259
  );
2193
2260
  }
2194
2261
  });
2195
2262
  }
2196
- function compilePolicy({ expression, table, resolver, maxLinkDepth }) {
2197
- if (typeof expression === "string") {
2198
- return compilePredicateV2({ expression, table, resolver, maxLinkDepth });
2199
- }
2200
- const defaultPred = compilePredicateV2({ expression: expression.default, table, resolver, maxLinkDepth });
2263
+ function initialVisiting({ table, access }) {
2264
+ return (access ?? "read") === "read" ? [table] : [];
2265
+ }
2266
+ function compilePolicy({ expression, table, resolver, maxLinkDepth, access }) {
2267
+ return compilePolicyAt({
2268
+ expression,
2269
+ table,
2270
+ depth: 0,
2271
+ ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: initialVisiting({ table, access }) }
2272
+ });
2273
+ }
2274
+ function compilePolicyAt({ expression, table, ctx, depth }) {
2275
+ if (typeof expression === "string") return compilePredicateAt({ expression, table, ctx, depth });
2276
+ const defaultPred = compilePredicateAt({ expression: expression.default, table, ctx, depth });
2201
2277
  if (expression.variants.length === 0) return defaultPred;
2202
2278
  const branches = expression.variants.map((variant) => {
2203
2279
  const parsedWhen = parsePolicy(variant.when);
2204
2280
  assertClaimsOnlyWhen(parsedWhen);
2205
- const when = compilePredicateV2({ expression: parsedWhen, table, resolver, maxLinkDepth });
2206
- const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateV2({ expression: variant.policy, table, resolver, maxLinkDepth });
2281
+ const when = compilePredicateAt({ expression: parsedWhen, table, ctx, depth });
2282
+ const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateAt({ expression: variant.policy, table, ctx, depth });
2207
2283
  return { kind: "and", operands: [when, grant] };
2208
2284
  });
2209
2285
  return { kind: "or", operands: [defaultPred, ...branches] };
@@ -2419,12 +2495,6 @@ var PolicyInjector = class {
2419
2495
  if (root.op === "resolvedWrite") return this.injectResolvedWrite(root, returning);
2420
2496
  if (root.op === "insert" || root.op === "insertOne") {
2421
2497
  const insertPolicy = this.insert(table);
2422
- if (predContainsExists(insertPolicy)) {
2423
- throw new DocumentCompileError(
2424
- `table '${table}' cannot be inserted into: v2.0 insert checks are claims + inserted-values only, but its insert policy traverses a link (exists)`,
2425
- "insert-check-unrepresentable"
2426
- );
2427
- }
2428
2498
  const pins = (this.ctx.pins.get(table) ?? []).map(
2429
2499
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2430
2500
  );
@@ -2439,22 +2509,14 @@ var PolicyInjector = class {
2439
2509
  const updatePolicy = this.update(table);
2440
2510
  const where = andPred(updatePolicy, root.where ? this.injectDocPred(root.where, table) : void 0);
2441
2511
  if (root.op === "update" || root.op === "updateByPk") {
2442
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2443
- return { ...root, where, ...check, returning };
2512
+ return { ...root, where, insertCheck: updatePolicy, returning };
2444
2513
  }
2445
2514
  if (root.op === "cowOverride") {
2446
2515
  const insertPolicy = this.insert(table);
2447
- if (predContainsExists(insertPolicy)) {
2448
- throw new DocumentCompileError(
2449
- `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)`,
2450
- "insert-check-unrepresentable"
2451
- );
2452
- }
2453
2516
  const pins = (this.ctx.pins.get(table) ?? []).map(
2454
2517
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2455
2518
  );
2456
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2457
- return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, ...check, returning };
2519
+ return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, insertCheck: updatePolicy, returning };
2458
2520
  }
2459
2521
  return { ...root, where, returning };
2460
2522
  }
@@ -2676,7 +2738,7 @@ function scopePolicy(pred, namespace) {
2676
2738
  switch (pred.kind) {
2677
2739
  case "capability":
2678
2740
  case "role":
2679
- return { ...pred, namespace };
2741
+ return pred.namespace ? pred : { ...pred, namespace };
2680
2742
  case "and":
2681
2743
  case "or":
2682
2744
  return { ...pred, operands: pred.operands.map((p) => scopePolicy(p, namespace)) };
@@ -2885,7 +2947,14 @@ function buildCompileContext(rawCanonical) {
2885
2947
  const updatePolicies = /* @__PURE__ */ new Map();
2886
2948
  const pins = /* @__PURE__ */ new Map();
2887
2949
  const splitWritePolicyTables = /* @__PURE__ */ new Set();
2888
- const resolver = { tables };
2950
+ const readPolicyExpressions = new Map(relations.map((relation) => [relation.table, relation.policies.read]));
2951
+ const resolver = {
2952
+ tables,
2953
+ readPolicy: (table) => readPolicyExpressions.get(table),
2954
+ // In a composed namespace, an imported table's read policy (inlined by
2955
+ // visible()) is checked against the namespace that owns it.
2956
+ ...normalized.imports?.length ? { policyNamespace: (table) => owners.get(table)?.namespace } : {}
2957
+ };
2889
2958
  for (const table of relations) {
2890
2959
  const isView = viewNames.has(table.table);
2891
2960
  const compilePolicyFor = (access, store) => {
@@ -2894,7 +2963,7 @@ function buildCompileContext(rawCanonical) {
2894
2963
  return;
2895
2964
  }
2896
2965
  try {
2897
- const pred = compilePolicy({ expression, table: table.table, resolver });
2966
+ const pred = compilePolicy({ expression, table: table.table, resolver, access });
2898
2967
  store.set(table.table, normalized.imports?.length ? scopePolicy(pred, owners.get(table.table).namespace) : pred);
2899
2968
  } catch (error) {
2900
2969
  if (error instanceof PolicyCompileError || error instanceof PolicyParseError) {