@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 +100 -66
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +100 -66
- package/dist/cli.js.map +1 -1
- package/dist/compiler.cjs +107 -38
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +6 -4
- package/dist/compiler.d.ts +6 -4
- package/dist/compiler.js +107 -38
- package/dist/compiler.js.map +1 -1
- package/dist/predicate-v2.cjs +95 -19
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.d.cts +40 -3
- package/dist/predicate-v2.d.ts +40 -3
- package/dist/predicate-v2.js +95 -19
- package/dist/predicate-v2.js.map +1 -1
- package/package.json +1 -1
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
|
|
2552
|
+
function compilePredicateAt({ expression, table, ctx, depth }) {
|
|
2493
2553
|
const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
|
|
2494
|
-
|
|
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
|
-
|
|
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
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
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 =
|
|
2530
|
-
const grant = variant.allow === true ? { kind: "const", value: true } :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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) {
|