@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/compiler.cjs CHANGED
@@ -1123,14 +1123,20 @@ function requiredArtifactIrVersion(operations) {
1123
1123
  var CLAIMS_CONTRACT = {
1124
1124
  sub: "string",
1125
1125
  org: "string?",
1126
+ ext_subject: "string?",
1127
+ ext_email: "string?",
1126
1128
  roles: "string[]",
1127
1129
  capabilities: "string[]"
1128
1130
  };
1131
+ var EXTERNAL_POLICY_CLAIM_KEYS = ["ext_subject", "ext_email"];
1132
+ function isExternalPolicyClaimKey(key) {
1133
+ return EXTERNAL_POLICY_CLAIM_KEYS.includes(key);
1134
+ }
1129
1135
  var scalarLiteral = zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]);
1130
1136
  var valueIrSchema = zod.z.discriminatedUnion("kind", [
1131
1137
  zod.z.object({ kind: zod.z.literal("literal"), value: zod.z.union([scalarLiteral, zod.z.array(scalarLiteral)]) }),
1132
1138
  zod.z.object({ kind: zod.z.literal("variable"), name: zod.z.string() }),
1133
- zod.z.object({ kind: zod.z.literal("claim"), key: zod.z.enum(["sub", "org"]) })
1139
+ zod.z.object({ kind: zod.z.literal("claim"), key: zod.z.enum(["sub", "org", ...EXTERNAL_POLICY_CLAIM_KEYS]) })
1134
1140
  ]);
1135
1141
  var CMP_OPS = [
1136
1142
  "eq",
@@ -1850,6 +1856,7 @@ var Parser2 = class {
1850
1856
  return node;
1851
1857
  }
1852
1858
  if (this.peekKeyword("exists")) return this.parseExists();
1859
+ if (this.peekKeyword("visible") && this.tokens[this.index + 1]?.kind === "lparen") return this.parseVisible();
1853
1860
  return this.parseComparison();
1854
1861
  }
1855
1862
  parseBooleanCall(fn) {
@@ -1869,6 +1876,13 @@ var Parser2 = class {
1869
1876
  this.expect("rparen", "')' closing exists(...)");
1870
1877
  return { kind: "exists", path, where };
1871
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
+ }
1872
1886
  parseComparison() {
1873
1887
  const left = this.parseOperand();
1874
1888
  const operator = this.expect("operator", "a comparison operator");
@@ -1984,6 +1998,24 @@ function parsePolicy(source) {
1984
1998
 
1985
1999
  // src/predicate-v2/compile.ts
1986
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
+ }
1987
2019
  var CMP_BY_OP = {
1988
2020
  "=": "eq",
1989
2021
  "!=": "neq",
@@ -2017,7 +2049,7 @@ function resolveLink(info, ref) {
2017
2049
  function columnCandidates(info) {
2018
2050
  return [...info.columns.map((c) => c.name), ...info.links.map((l) => l.name)];
2019
2051
  }
2020
- var PolicyCompiler = class {
2052
+ var PolicyCompiler = class _PolicyCompiler {
2021
2053
  constructor(ctx) {
2022
2054
  this.ctx = ctx;
2023
2055
  }
@@ -2029,13 +2061,16 @@ var PolicyCompiler = class {
2029
2061
  case "or":
2030
2062
  return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
2031
2063
  case "not":
2032
- return { kind: "not", operand: this.compile(node.operand, table, depth) };
2064
+ assertNoNegatedExternalClaim(node.operand);
2065
+ return { kind: "not", operand: new _PolicyCompiler({ ...this.ctx, negated: true }).compile(node.operand, table, depth) };
2033
2066
  case "capability":
2034
2067
  return { kind: "capability", key: node.key };
2035
2068
  case "role":
2036
2069
  return { kind: "role", key: node.key };
2037
2070
  case "exists":
2038
2071
  return this.compileExists(node.path, node.where, table, depth);
2072
+ case "visible":
2073
+ return this.compileVisible(node.path, table, depth);
2039
2074
  case "compare":
2040
2075
  return this.compileCompare(node, table, depth);
2041
2076
  }
@@ -2049,6 +2084,36 @@ var PolicyCompiler = class {
2049
2084
  }
2050
2085
  return pred;
2051
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
+ }
2052
2117
  resolveLinkPath(path, table, depth, { toOneOnly }) {
2053
2118
  const links = [];
2054
2119
  let current = table;
@@ -2108,6 +2173,10 @@ var PolicyCompiler = class {
2108
2173
  columnCandidates(info)
2109
2174
  );
2110
2175
  }
2176
+ if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
2177
+ if (this.ctx.negated) throw negatedExternalClaim(valueSide.key);
2178
+ assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
2179
+ }
2111
2180
  let cmp = this.buildCmp(op, column, valueSide);
2112
2181
  for (let i = links.length - 1; i >= 0; i -= 1) {
2113
2182
  cmp = { kind: "exists", link: links[i].name, where: cmp };
@@ -2128,10 +2197,45 @@ var PolicyCompiler = class {
2128
2197
  return { kind: "cmp", op: CMP_BY_OP[op], column, value };
2129
2198
  }
2130
2199
  };
2200
+ function assertExternalClaimComparison({ op, key, column }) {
2201
+ if (op !== "=") {
2202
+ throw new PolicyCompileError(
2203
+ `claim('${key}') supports = comparisons only, got '${op}'`,
2204
+ "unsupported-comparison"
2205
+ );
2206
+ }
2207
+ if (column?.type !== "text") {
2208
+ const actual = column?.type === void 0 ? "of unknown type" : typeof column.type === "string" ? column.type : "an enum";
2209
+ throw new PolicyCompileError(
2210
+ `claim('${key}') is text and compares to a text column only \u2014 '${column?.name ?? "?"}' is ${actual}`,
2211
+ "claim-type-mismatch"
2212
+ );
2213
+ }
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
+ }
2221
+ function assertNoNegatedExternalClaim(operand) {
2222
+ walkPolicy(operand, (node) => {
2223
+ if (node.kind !== "compare") return;
2224
+ const claim = [node.left, node.right].find((side) => side.kind === "claim" && isExternalPolicyClaimKey(side.key));
2225
+ if (claim?.kind === "claim") throw negatedExternalClaim(claim.key);
2226
+ });
2227
+ }
2131
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 }) {
2132
2237
  const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
2133
- const compiler = new PolicyCompiler({ resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH });
2134
- return compiler.compile(parsed.ast, table, 0);
2238
+ return new PolicyCompiler(ctx).compile(parsed.ast, table, depth);
2135
2239
  }
2136
2240
  function assertClaimsOnlyWhen(parsed) {
2137
2241
  walkPolicy(parsed.ast, (node) => {
@@ -2148,25 +2252,34 @@ function assertClaimsOnlyWhen(parsed) {
2148
2252
  "when-not-claims-only"
2149
2253
  );
2150
2254
  }
2151
- if (node.kind === "exists") {
2255
+ if (node.kind === "exists" || node.kind === "visible") {
2152
2256
  throw new PolicyCompileError(
2153
- "variant 'when' guards are claims-only \u2014 exists() is not allowed",
2257
+ `variant 'when' guards are claims-only \u2014 ${node.kind}() is not allowed`,
2154
2258
  "when-not-claims-only"
2155
2259
  );
2156
2260
  }
2157
2261
  });
2158
2262
  }
2159
- function compilePolicy({ expression, table, resolver, maxLinkDepth }) {
2160
- if (typeof expression === "string") {
2161
- return compilePredicateV2({ expression, table, resolver, maxLinkDepth });
2162
- }
2163
- 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 });
2164
2277
  if (expression.variants.length === 0) return defaultPred;
2165
2278
  const branches = expression.variants.map((variant) => {
2166
2279
  const parsedWhen = parsePolicy(variant.when);
2167
2280
  assertClaimsOnlyWhen(parsedWhen);
2168
- const when = compilePredicateV2({ expression: parsedWhen, table, resolver, maxLinkDepth });
2169
- 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 });
2170
2283
  return { kind: "and", operands: [when, grant] };
2171
2284
  });
2172
2285
  return { kind: "or", operands: [defaultPred, ...branches] };
@@ -2382,12 +2495,6 @@ var PolicyInjector = class {
2382
2495
  if (root.op === "resolvedWrite") return this.injectResolvedWrite(root, returning);
2383
2496
  if (root.op === "insert" || root.op === "insertOne") {
2384
2497
  const insertPolicy = this.insert(table);
2385
- if (predContainsExists(insertPolicy)) {
2386
- throw new DocumentCompileError(
2387
- `table '${table}' cannot be inserted into: v2.0 insert checks are claims + inserted-values only, but its insert policy traverses a link (exists)`,
2388
- "insert-check-unrepresentable"
2389
- );
2390
- }
2391
2498
  const pins = (this.ctx.pins.get(table) ?? []).map(
2392
2499
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2393
2500
  );
@@ -2402,22 +2509,14 @@ var PolicyInjector = class {
2402
2509
  const updatePolicy = this.update(table);
2403
2510
  const where = andPred(updatePolicy, root.where ? this.injectDocPred(root.where, table) : void 0);
2404
2511
  if (root.op === "update" || root.op === "updateByPk") {
2405
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2406
- return { ...root, where, ...check, returning };
2512
+ return { ...root, where, insertCheck: updatePolicy, returning };
2407
2513
  }
2408
2514
  if (root.op === "cowOverride") {
2409
2515
  const insertPolicy = this.insert(table);
2410
- if (predContainsExists(insertPolicy)) {
2411
- throw new DocumentCompileError(
2412
- `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)`,
2413
- "insert-check-unrepresentable"
2414
- );
2415
- }
2416
2516
  const pins = (this.ctx.pins.get(table) ?? []).map(
2417
2517
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2418
2518
  );
2419
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2420
- return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, ...check, returning };
2519
+ return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, insertCheck: updatePolicy, returning };
2421
2520
  }
2422
2521
  return { ...root, where, returning };
2423
2522
  }
@@ -2639,7 +2738,7 @@ function scopePolicy(pred, namespace) {
2639
2738
  switch (pred.kind) {
2640
2739
  case "capability":
2641
2740
  case "role":
2642
- return { ...pred, namespace };
2741
+ return pred.namespace ? pred : { ...pred, namespace };
2643
2742
  case "and":
2644
2743
  case "or":
2645
2744
  return { ...pred, operands: pred.operands.map((p) => scopePolicy(p, namespace)) };
@@ -2848,7 +2947,14 @@ function buildCompileContext(rawCanonical) {
2848
2947
  const updatePolicies = /* @__PURE__ */ new Map();
2849
2948
  const pins = /* @__PURE__ */ new Map();
2850
2949
  const splitWritePolicyTables = /* @__PURE__ */ new Set();
2851
- 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
+ };
2852
2958
  for (const table of relations) {
2853
2959
  const isView = viewNames.has(table.table);
2854
2960
  const compilePolicyFor = (access, store) => {
@@ -2857,7 +2963,7 @@ function buildCompileContext(rawCanonical) {
2857
2963
  return;
2858
2964
  }
2859
2965
  try {
2860
- const pred = compilePolicy({ expression, table: table.table, resolver });
2966
+ const pred = compilePolicy({ expression, table: table.table, resolver, access });
2861
2967
  store.set(table.table, normalized.imports?.length ? scopePolicy(pred, owners.get(table.table).namespace) : pred);
2862
2968
  } catch (error) {
2863
2969
  if (error instanceof PolicyCompileError || error instanceof PolicyParseError) {