@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/cli.cjs CHANGED
@@ -2180,6 +2180,7 @@ var Parser2 = class {
2180
2180
  return node;
2181
2181
  }
2182
2182
  if (this.peekKeyword("exists")) return this.parseExists();
2183
+ if (this.peekKeyword("visible") && this.tokens[this.index + 1]?.kind === "lparen") return this.parseVisible();
2183
2184
  return this.parseComparison();
2184
2185
  }
2185
2186
  parseBooleanCall(fn) {
@@ -2199,6 +2200,13 @@ var Parser2 = class {
2199
2200
  this.expect("rparen", "')' closing exists(...)");
2200
2201
  return { kind: "exists", path, where };
2201
2202
  }
2203
+ parseVisible() {
2204
+ this.advance();
2205
+ this.expect("lparen", "'(' after visible");
2206
+ const path = this.parsePath("a link path in visible(...)");
2207
+ this.expect("rparen", "')' closing visible(...)");
2208
+ return { kind: "visible", path };
2209
+ }
2202
2210
  parseComparison() {
2203
2211
  const left = this.parseOperand();
2204
2212
  const operator = this.expect("operator", "a comparison operator");
@@ -2314,6 +2322,24 @@ function parsePolicy(source) {
2314
2322
 
2315
2323
  // src/predicate-v2/compile.ts
2316
2324
  var POLICY_MAX_LINK_DEPTH = 3;
2325
+ function scopeToNamespace(pred, namespace) {
2326
+ switch (pred.kind) {
2327
+ case "capability":
2328
+ case "role":
2329
+ return pred.namespace ? pred : { ...pred, namespace };
2330
+ case "and":
2331
+ case "or":
2332
+ return { ...pred, operands: pred.operands.map((operand) => scopeToNamespace(operand, namespace)) };
2333
+ case "not":
2334
+ return { ...pred, operand: scopeToNamespace(pred.operand, namespace) };
2335
+ case "exists":
2336
+ return { ...pred, where: scopeToNamespace(pred.where, namespace) };
2337
+ case "aggPredicate":
2338
+ return pred.where ? { ...pred, where: scopeToNamespace(pred.where, namespace) } : pred;
2339
+ default:
2340
+ return pred;
2341
+ }
2342
+ }
2317
2343
  var CMP_BY_OP = {
2318
2344
  "=": "eq",
2319
2345
  "!=": "neq",
@@ -2347,7 +2373,7 @@ function resolveLink(info, ref) {
2347
2373
  function columnCandidates(info) {
2348
2374
  return [...info.columns.map((c) => c.name), ...info.links.map((l) => l.name)];
2349
2375
  }
2350
- var PolicyCompiler = class {
2376
+ var PolicyCompiler = class _PolicyCompiler {
2351
2377
  constructor(ctx) {
2352
2378
  this.ctx = ctx;
2353
2379
  }
@@ -2360,13 +2386,15 @@ var PolicyCompiler = class {
2360
2386
  return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
2361
2387
  case "not":
2362
2388
  assertNoNegatedExternalClaim(node.operand);
2363
- return { kind: "not", operand: this.compile(node.operand, table, depth) };
2389
+ return { kind: "not", operand: new _PolicyCompiler({ ...this.ctx, negated: true }).compile(node.operand, table, depth) };
2364
2390
  case "capability":
2365
2391
  return { kind: "capability", key: node.key };
2366
2392
  case "role":
2367
2393
  return { kind: "role", key: node.key };
2368
2394
  case "exists":
2369
2395
  return this.compileExists(node.path, node.where, table, depth);
2396
+ case "visible":
2397
+ return this.compileVisible(node.path, table, depth);
2370
2398
  case "compare":
2371
2399
  return this.compileCompare(node, table, depth);
2372
2400
  }
@@ -2380,6 +2408,36 @@ var PolicyCompiler = class {
2380
2408
  }
2381
2409
  return pred;
2382
2410
  }
2411
+ /** `visible(path)` → `exists(path, <final target's read policy>)`. */
2412
+ compileVisible(path, table, depth) {
2413
+ const { links, finalTable } = this.resolveLinkPath(path, table, depth, { toOneOnly: false });
2414
+ const readPolicy = this.ctx.resolver.readPolicy;
2415
+ if (!readPolicy) {
2416
+ throw new PolicyCompileError(
2417
+ `visible(${path.join(".")}) needs the target's read policy, which this compile context does not provide`,
2418
+ "visible-unresolvable"
2419
+ );
2420
+ }
2421
+ if (this.ctx.visiting.includes(finalTable)) {
2422
+ throw new PolicyCompileError(
2423
+ `visible(${path.join(".")}) on '${table}' reaches '${finalTable}' whose read policy is already being expanded (${[...this.ctx.visiting, finalTable].join(" \u2192 ")})`,
2424
+ "visible-cycle"
2425
+ );
2426
+ }
2427
+ const expression = readPolicy(finalTable);
2428
+ let pred = expression === void 0 ? { kind: "const", value: true } : compilePolicyAt({
2429
+ expression,
2430
+ table: finalTable,
2431
+ ctx: { ...this.ctx, visiting: [...this.ctx.visiting, finalTable] },
2432
+ depth: depth + links.length
2433
+ });
2434
+ const owner = this.ctx.resolver.policyNamespace?.(finalTable);
2435
+ if (owner) pred = scopeToNamespace(pred, owner);
2436
+ for (let i = links.length - 1; i >= 0; i -= 1) {
2437
+ pred = { kind: "exists", link: links[i].name, where: pred };
2438
+ }
2439
+ return pred;
2440
+ }
2383
2441
  resolveLinkPath(path, table, depth, { toOneOnly }) {
2384
2442
  const links = [];
2385
2443
  let current = table;
@@ -2440,6 +2498,7 @@ var PolicyCompiler = class {
2440
2498
  );
2441
2499
  }
2442
2500
  if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
2501
+ if (this.ctx.negated) throw negatedExternalClaim(valueSide.key);
2443
2502
  assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
2444
2503
  }
2445
2504
  let cmp = this.buildCmp(op, column, valueSide);
@@ -2477,22 +2536,22 @@ function assertExternalClaimComparison({ op, key, column }) {
2477
2536
  );
2478
2537
  }
2479
2538
  }
2539
+ function negatedExternalClaim(key) {
2540
+ return new PolicyCompileError(
2541
+ `claim('${key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
2542
+ "unsupported-comparison"
2543
+ );
2544
+ }
2480
2545
  function assertNoNegatedExternalClaim(operand) {
2481
2546
  walkPolicy(operand, (node) => {
2482
2547
  if (node.kind !== "compare") return;
2483
2548
  const claim = [node.left, node.right].find((side) => side.kind === "claim" && isExternalPolicyClaimKey(side.key));
2484
- if (claim?.kind === "claim") {
2485
- throw new PolicyCompileError(
2486
- `claim('${claim.key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
2487
- "unsupported-comparison"
2488
- );
2489
- }
2549
+ if (claim?.kind === "claim") throw negatedExternalClaim(claim.key);
2490
2550
  });
2491
2551
  }
2492
- function compilePredicateV2({ expression, table, resolver, maxLinkDepth }) {
2552
+ function compilePredicateAt({ expression, table, ctx, depth }) {
2493
2553
  const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
2494
- const compiler = new PolicyCompiler({ resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH });
2495
- return compiler.compile(parsed.ast, table, 0);
2554
+ return new PolicyCompiler(ctx).compile(parsed.ast, table, depth);
2496
2555
  }
2497
2556
  function assertClaimsOnlyWhen(parsed) {
2498
2557
  walkPolicy(parsed.ast, (node) => {
@@ -2509,25 +2568,34 @@ function assertClaimsOnlyWhen(parsed) {
2509
2568
  "when-not-claims-only"
2510
2569
  );
2511
2570
  }
2512
- if (node.kind === "exists") {
2571
+ if (node.kind === "exists" || node.kind === "visible") {
2513
2572
  throw new PolicyCompileError(
2514
- "variant 'when' guards are claims-only \u2014 exists() is not allowed",
2573
+ `variant 'when' guards are claims-only \u2014 ${node.kind}() is not allowed`,
2515
2574
  "when-not-claims-only"
2516
2575
  );
2517
2576
  }
2518
2577
  });
2519
2578
  }
2520
- function compilePolicy({ expression, table, resolver, maxLinkDepth }) {
2521
- if (typeof expression === "string") {
2522
- return compilePredicateV2({ expression, table, resolver, maxLinkDepth });
2523
- }
2524
- const defaultPred = compilePredicateV2({ expression: expression.default, table, resolver, maxLinkDepth });
2579
+ function initialVisiting({ table, access }) {
2580
+ return (access ?? "read") === "read" ? [table] : [];
2581
+ }
2582
+ function compilePolicy({ expression, table, resolver, maxLinkDepth, access }) {
2583
+ return compilePolicyAt({
2584
+ expression,
2585
+ table,
2586
+ depth: 0,
2587
+ ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: initialVisiting({ table, access }) }
2588
+ });
2589
+ }
2590
+ function compilePolicyAt({ expression, table, ctx, depth }) {
2591
+ if (typeof expression === "string") return compilePredicateAt({ expression, table, ctx, depth });
2592
+ const defaultPred = compilePredicateAt({ expression: expression.default, table, ctx, depth });
2525
2593
  if (expression.variants.length === 0) return defaultPred;
2526
2594
  const branches = expression.variants.map((variant) => {
2527
2595
  const parsedWhen = parsePolicy(variant.when);
2528
2596
  assertClaimsOnlyWhen(parsedWhen);
2529
- const when = compilePredicateV2({ expression: parsedWhen, table, resolver, maxLinkDepth });
2530
- const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateV2({ expression: variant.policy, table, resolver, maxLinkDepth });
2597
+ const when = compilePredicateAt({ expression: parsedWhen, table, ctx, depth });
2598
+ const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateAt({ expression: variant.policy, table, ctx, depth });
2531
2599
  return { kind: "and", operands: [when, grant] };
2532
2600
  });
2533
2601
  return { kind: "or", operands: [defaultPred, ...branches] };
@@ -2565,33 +2633,6 @@ function andPred(...preds) {
2565
2633
  if (operands.length === 1) return operands[0];
2566
2634
  return { kind: "and", operands };
2567
2635
  }
2568
- function walkPred(pred, visit2) {
2569
- visit2(pred);
2570
- switch (pred.kind) {
2571
- case "and":
2572
- case "or":
2573
- for (const operand of pred.operands) walkPred(operand, visit2);
2574
- return;
2575
- case "not":
2576
- walkPred(pred.operand, visit2);
2577
- return;
2578
- case "exists":
2579
- walkPred(pred.where, visit2);
2580
- return;
2581
- case "aggPredicate":
2582
- if (pred.where) walkPred(pred.where, visit2);
2583
- return;
2584
- default:
2585
- return;
2586
- }
2587
- }
2588
- function predContainsExists(pred) {
2589
- let found = false;
2590
- walkPred(pred, (node) => {
2591
- if (node.kind === "exists" || node.kind === "aggPredicate") found = true;
2592
- });
2593
- return found;
2594
- }
2595
2636
 
2596
2637
  // src/compiler-gql/inject.ts
2597
2638
  function collectPinnedColumns(writePolicy) {
@@ -2735,12 +2776,6 @@ var PolicyInjector = class {
2735
2776
  if (root.op === "resolvedWrite") return this.injectResolvedWrite(root, returning);
2736
2777
  if (root.op === "insert" || root.op === "insertOne") {
2737
2778
  const insertPolicy = this.insert(table);
2738
- if (predContainsExists(insertPolicy)) {
2739
- throw new DocumentCompileError(
2740
- `table '${table}' cannot be inserted into: v2.0 insert checks are claims + inserted-values only, but its insert policy traverses a link (exists)`,
2741
- "insert-check-unrepresentable"
2742
- );
2743
- }
2744
2779
  const pins = (this.ctx.pins.get(table) ?? []).map(
2745
2780
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2746
2781
  );
@@ -2755,22 +2790,14 @@ var PolicyInjector = class {
2755
2790
  const updatePolicy = this.update(table);
2756
2791
  const where = andPred(updatePolicy, root.where ? this.injectDocPred(root.where, table) : void 0);
2757
2792
  if (root.op === "update" || root.op === "updateByPk") {
2758
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2759
- return { ...root, where, ...check, returning };
2793
+ return { ...root, where, insertCheck: updatePolicy, returning };
2760
2794
  }
2761
2795
  if (root.op === "cowOverride") {
2762
2796
  const insertPolicy = this.insert(table);
2763
- if (predContainsExists(insertPolicy)) {
2764
- throw new DocumentCompileError(
2765
- `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)`,
2766
- "insert-check-unrepresentable"
2767
- );
2768
- }
2769
2797
  const pins = (this.ctx.pins.get(table) ?? []).map(
2770
2798
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2771
2799
  );
2772
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2773
- return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, ...check, returning };
2800
+ return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, insertCheck: updatePolicy, returning };
2774
2801
  }
2775
2802
  return { ...root, where, returning };
2776
2803
  }
@@ -3004,7 +3031,7 @@ function scopePolicy(pred, namespace) {
3004
3031
  switch (pred.kind) {
3005
3032
  case "capability":
3006
3033
  case "role":
3007
- return { ...pred, namespace };
3034
+ return pred.namespace ? pred : { ...pred, namespace };
3008
3035
  case "and":
3009
3036
  case "or":
3010
3037
  return { ...pred, operands: pred.operands.map((p) => scopePolicy(p, namespace)) };
@@ -3213,7 +3240,14 @@ function buildCompileContext(rawCanonical) {
3213
3240
  const updatePolicies = /* @__PURE__ */ new Map();
3214
3241
  const pins = /* @__PURE__ */ new Map();
3215
3242
  const splitWritePolicyTables = /* @__PURE__ */ new Set();
3216
- const resolver = { tables };
3243
+ const readPolicyExpressions = new Map(relations.map((relation) => [relation.table, relation.policies.read]));
3244
+ const resolver = {
3245
+ tables,
3246
+ readPolicy: (table) => readPolicyExpressions.get(table),
3247
+ // In a composed namespace, an imported table's read policy (inlined by
3248
+ // visible()) is checked against the namespace that owns it.
3249
+ ...normalized.imports?.length ? { policyNamespace: (table) => owners.get(table)?.namespace } : {}
3250
+ };
3217
3251
  for (const table of relations) {
3218
3252
  const isView = viewNames.has(table.table);
3219
3253
  const compilePolicyFor = (access, store) => {
@@ -3222,7 +3256,7 @@ function buildCompileContext(rawCanonical) {
3222
3256
  return;
3223
3257
  }
3224
3258
  try {
3225
- const pred = compilePolicy({ expression, table: table.table, resolver });
3259
+ const pred = compilePolicy({ expression, table: table.table, resolver, access });
3226
3260
  store.set(table.table, normalized.imports?.length ? scopePolicy(pred, owners.get(table.table).namespace) : pred);
3227
3261
  } catch (error) {
3228
3262
  if (error instanceof PolicyCompileError || error instanceof PolicyParseError) {