@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.
package/dist/cli.cjs CHANGED
@@ -1429,14 +1429,20 @@ function requiredArtifactIrVersion(operations) {
1429
1429
  var CLAIMS_CONTRACT = {
1430
1430
  sub: "string",
1431
1431
  org: "string?",
1432
+ ext_subject: "string?",
1433
+ ext_email: "string?",
1432
1434
  roles: "string[]",
1433
1435
  capabilities: "string[]"
1434
1436
  };
1437
+ var EXTERNAL_POLICY_CLAIM_KEYS = ["ext_subject", "ext_email"];
1438
+ function isExternalPolicyClaimKey(key) {
1439
+ return EXTERNAL_POLICY_CLAIM_KEYS.includes(key);
1440
+ }
1435
1441
  var scalarLiteral = zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]);
1436
1442
  var valueIrSchema = zod.z.discriminatedUnion("kind", [
1437
1443
  zod.z.object({ kind: zod.z.literal("literal"), value: zod.z.union([scalarLiteral, zod.z.array(scalarLiteral)]) }),
1438
1444
  zod.z.object({ kind: zod.z.literal("variable"), name: zod.z.string() }),
1439
- zod.z.object({ kind: zod.z.literal("claim"), key: zod.z.enum(["sub", "org"]) })
1445
+ zod.z.object({ kind: zod.z.literal("claim"), key: zod.z.enum(["sub", "org", ...EXTERNAL_POLICY_CLAIM_KEYS]) })
1440
1446
  ]);
1441
1447
  var CMP_OPS = [
1442
1448
  "eq",
@@ -2174,6 +2180,7 @@ var Parser2 = class {
2174
2180
  return node;
2175
2181
  }
2176
2182
  if (this.peekKeyword("exists")) return this.parseExists();
2183
+ if (this.peekKeyword("visible") && this.tokens[this.index + 1]?.kind === "lparen") return this.parseVisible();
2177
2184
  return this.parseComparison();
2178
2185
  }
2179
2186
  parseBooleanCall(fn) {
@@ -2193,6 +2200,13 @@ var Parser2 = class {
2193
2200
  this.expect("rparen", "')' closing exists(...)");
2194
2201
  return { kind: "exists", path, where };
2195
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
+ }
2196
2210
  parseComparison() {
2197
2211
  const left = this.parseOperand();
2198
2212
  const operator = this.expect("operator", "a comparison operator");
@@ -2308,6 +2322,24 @@ function parsePolicy(source) {
2308
2322
 
2309
2323
  // src/predicate-v2/compile.ts
2310
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
+ }
2311
2343
  var CMP_BY_OP = {
2312
2344
  "=": "eq",
2313
2345
  "!=": "neq",
@@ -2341,7 +2373,7 @@ function resolveLink(info, ref) {
2341
2373
  function columnCandidates(info) {
2342
2374
  return [...info.columns.map((c) => c.name), ...info.links.map((l) => l.name)];
2343
2375
  }
2344
- var PolicyCompiler = class {
2376
+ var PolicyCompiler = class _PolicyCompiler {
2345
2377
  constructor(ctx) {
2346
2378
  this.ctx = ctx;
2347
2379
  }
@@ -2353,13 +2385,16 @@ var PolicyCompiler = class {
2353
2385
  case "or":
2354
2386
  return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
2355
2387
  case "not":
2356
- return { kind: "not", operand: this.compile(node.operand, table, depth) };
2388
+ assertNoNegatedExternalClaim(node.operand);
2389
+ return { kind: "not", operand: new _PolicyCompiler({ ...this.ctx, negated: true }).compile(node.operand, table, depth) };
2357
2390
  case "capability":
2358
2391
  return { kind: "capability", key: node.key };
2359
2392
  case "role":
2360
2393
  return { kind: "role", key: node.key };
2361
2394
  case "exists":
2362
2395
  return this.compileExists(node.path, node.where, table, depth);
2396
+ case "visible":
2397
+ return this.compileVisible(node.path, table, depth);
2363
2398
  case "compare":
2364
2399
  return this.compileCompare(node, table, depth);
2365
2400
  }
@@ -2373,6 +2408,36 @@ var PolicyCompiler = class {
2373
2408
  }
2374
2409
  return pred;
2375
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
+ }
2376
2441
  resolveLinkPath(path, table, depth, { toOneOnly }) {
2377
2442
  const links = [];
2378
2443
  let current = table;
@@ -2432,6 +2497,10 @@ var PolicyCompiler = class {
2432
2497
  columnCandidates(info)
2433
2498
  );
2434
2499
  }
2500
+ if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
2501
+ if (this.ctx.negated) throw negatedExternalClaim(valueSide.key);
2502
+ assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
2503
+ }
2435
2504
  let cmp = this.buildCmp(op, column, valueSide);
2436
2505
  for (let i = links.length - 1; i >= 0; i -= 1) {
2437
2506
  cmp = { kind: "exists", link: links[i].name, where: cmp };
@@ -2452,10 +2521,37 @@ var PolicyCompiler = class {
2452
2521
  return { kind: "cmp", op: CMP_BY_OP[op], column, value };
2453
2522
  }
2454
2523
  };
2455
- function compilePredicateV2({ expression, table, resolver, maxLinkDepth }) {
2524
+ function assertExternalClaimComparison({ op, key, column }) {
2525
+ if (op !== "=") {
2526
+ throw new PolicyCompileError(
2527
+ `claim('${key}') supports = comparisons only, got '${op}'`,
2528
+ "unsupported-comparison"
2529
+ );
2530
+ }
2531
+ if (column?.type !== "text") {
2532
+ const actual = column?.type === void 0 ? "of unknown type" : typeof column.type === "string" ? column.type : "an enum";
2533
+ throw new PolicyCompileError(
2534
+ `claim('${key}') is text and compares to a text column only \u2014 '${column?.name ?? "?"}' is ${actual}`,
2535
+ "claim-type-mismatch"
2536
+ );
2537
+ }
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
+ }
2545
+ function assertNoNegatedExternalClaim(operand) {
2546
+ walkPolicy(operand, (node) => {
2547
+ if (node.kind !== "compare") return;
2548
+ const claim = [node.left, node.right].find((side) => side.kind === "claim" && isExternalPolicyClaimKey(side.key));
2549
+ if (claim?.kind === "claim") throw negatedExternalClaim(claim.key);
2550
+ });
2551
+ }
2552
+ function compilePredicateAt({ expression, table, ctx, depth }) {
2456
2553
  const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
2457
- const compiler = new PolicyCompiler({ resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH });
2458
- return compiler.compile(parsed.ast, table, 0);
2554
+ return new PolicyCompiler(ctx).compile(parsed.ast, table, depth);
2459
2555
  }
2460
2556
  function assertClaimsOnlyWhen(parsed) {
2461
2557
  walkPolicy(parsed.ast, (node) => {
@@ -2472,25 +2568,34 @@ function assertClaimsOnlyWhen(parsed) {
2472
2568
  "when-not-claims-only"
2473
2569
  );
2474
2570
  }
2475
- if (node.kind === "exists") {
2571
+ if (node.kind === "exists" || node.kind === "visible") {
2476
2572
  throw new PolicyCompileError(
2477
- "variant 'when' guards are claims-only \u2014 exists() is not allowed",
2573
+ `variant 'when' guards are claims-only \u2014 ${node.kind}() is not allowed`,
2478
2574
  "when-not-claims-only"
2479
2575
  );
2480
2576
  }
2481
2577
  });
2482
2578
  }
2483
- function compilePolicy({ expression, table, resolver, maxLinkDepth }) {
2484
- if (typeof expression === "string") {
2485
- return compilePredicateV2({ expression, table, resolver, maxLinkDepth });
2486
- }
2487
- 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 });
2488
2593
  if (expression.variants.length === 0) return defaultPred;
2489
2594
  const branches = expression.variants.map((variant) => {
2490
2595
  const parsedWhen = parsePolicy(variant.when);
2491
2596
  assertClaimsOnlyWhen(parsedWhen);
2492
- const when = compilePredicateV2({ expression: parsedWhen, table, resolver, maxLinkDepth });
2493
- 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 });
2494
2599
  return { kind: "and", operands: [when, grant] };
2495
2600
  });
2496
2601
  return { kind: "or", operands: [defaultPred, ...branches] };
@@ -2528,33 +2633,6 @@ function andPred(...preds) {
2528
2633
  if (operands.length === 1) return operands[0];
2529
2634
  return { kind: "and", operands };
2530
2635
  }
2531
- function walkPred(pred, visit2) {
2532
- visit2(pred);
2533
- switch (pred.kind) {
2534
- case "and":
2535
- case "or":
2536
- for (const operand of pred.operands) walkPred(operand, visit2);
2537
- return;
2538
- case "not":
2539
- walkPred(pred.operand, visit2);
2540
- return;
2541
- case "exists":
2542
- walkPred(pred.where, visit2);
2543
- return;
2544
- case "aggPredicate":
2545
- if (pred.where) walkPred(pred.where, visit2);
2546
- return;
2547
- default:
2548
- return;
2549
- }
2550
- }
2551
- function predContainsExists(pred) {
2552
- let found = false;
2553
- walkPred(pred, (node) => {
2554
- if (node.kind === "exists" || node.kind === "aggPredicate") found = true;
2555
- });
2556
- return found;
2557
- }
2558
2636
 
2559
2637
  // src/compiler-gql/inject.ts
2560
2638
  function collectPinnedColumns(writePolicy) {
@@ -2698,12 +2776,6 @@ var PolicyInjector = class {
2698
2776
  if (root.op === "resolvedWrite") return this.injectResolvedWrite(root, returning);
2699
2777
  if (root.op === "insert" || root.op === "insertOne") {
2700
2778
  const insertPolicy = this.insert(table);
2701
- if (predContainsExists(insertPolicy)) {
2702
- throw new DocumentCompileError(
2703
- `table '${table}' cannot be inserted into: v2.0 insert checks are claims + inserted-values only, but its insert policy traverses a link (exists)`,
2704
- "insert-check-unrepresentable"
2705
- );
2706
- }
2707
2779
  const pins = (this.ctx.pins.get(table) ?? []).map(
2708
2780
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2709
2781
  );
@@ -2718,22 +2790,14 @@ var PolicyInjector = class {
2718
2790
  const updatePolicy = this.update(table);
2719
2791
  const where = andPred(updatePolicy, root.where ? this.injectDocPred(root.where, table) : void 0);
2720
2792
  if (root.op === "update" || root.op === "updateByPk") {
2721
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2722
- return { ...root, where, ...check, returning };
2793
+ return { ...root, where, insertCheck: updatePolicy, returning };
2723
2794
  }
2724
2795
  if (root.op === "cowOverride") {
2725
2796
  const insertPolicy = this.insert(table);
2726
- if (predContainsExists(insertPolicy)) {
2727
- throw new DocumentCompileError(
2728
- `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)`,
2729
- "insert-check-unrepresentable"
2730
- );
2731
- }
2732
2797
  const pins = (this.ctx.pins.get(table) ?? []).map(
2733
2798
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2734
2799
  );
2735
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2736
- return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, ...check, returning };
2800
+ return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, insertCheck: updatePolicy, returning };
2737
2801
  }
2738
2802
  return { ...root, where, returning };
2739
2803
  }
@@ -2967,7 +3031,7 @@ function scopePolicy(pred, namespace) {
2967
3031
  switch (pred.kind) {
2968
3032
  case "capability":
2969
3033
  case "role":
2970
- return { ...pred, namespace };
3034
+ return pred.namespace ? pred : { ...pred, namespace };
2971
3035
  case "and":
2972
3036
  case "or":
2973
3037
  return { ...pred, operands: pred.operands.map((p) => scopePolicy(p, namespace)) };
@@ -3176,7 +3240,14 @@ function buildCompileContext(rawCanonical) {
3176
3240
  const updatePolicies = /* @__PURE__ */ new Map();
3177
3241
  const pins = /* @__PURE__ */ new Map();
3178
3242
  const splitWritePolicyTables = /* @__PURE__ */ new Set();
3179
- 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
+ };
3180
3251
  for (const table of relations) {
3181
3252
  const isView = viewNames.has(table.table);
3182
3253
  const compilePolicyFor = (access, store) => {
@@ -3185,7 +3256,7 @@ function buildCompileContext(rawCanonical) {
3185
3256
  return;
3186
3257
  }
3187
3258
  try {
3188
- const pred = compilePolicy({ expression, table: table.table, resolver });
3259
+ const pred = compilePolicy({ expression, table: table.table, resolver, access });
3189
3260
  store.set(table.table, normalized.imports?.length ? scopePolicy(pred, owners.get(table.table).namespace) : pred);
3190
3261
  } catch (error) {
3191
3262
  if (error instanceof PolicyCompileError || error instanceof PolicyParseError) {