@sequenceholdings/orm 2.3.0 → 2.5.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/authoring.cjs +53 -7
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +11 -7
- package/dist/authoring.d.ts +11 -7
- package/dist/authoring.js +53 -7
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +48 -2
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +72 -72
- package/dist/canonical.d.ts +72 -72
- package/dist/canonical.js +48 -2
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +337 -86
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +337 -86
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +493 -52
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +22 -7
- package/dist/compiler.d.ts +22 -7
- package/dist/compiler.js +493 -53
- package/dist/compiler.js.map +1 -1
- package/dist/{constants-C6EG6Wq7.d.cts → constants-Dou1RkTo.d.cts} +7 -2
- package/dist/{constants-C6EG6Wq7.d.ts → constants-Dou1RkTo.d.ts} +7 -2
- package/dist/dynamic-input.d.cts +1 -1
- package/dist/dynamic-input.d.ts +1 -1
- package/dist/index.cjs +329 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -58
- package/dist/index.d.ts +66 -58
- package/dist/index.js +329 -131
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +50 -3
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +71 -71
- package/dist/ir.d.ts +71 -71
- package/dist/ir.js +50 -3
- package/dist/ir.js.map +1 -1
- package/dist/predicate-v2.cjs +145 -22
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.d.cts +41 -4
- package/dist/predicate-v2.d.ts +41 -4
- package/dist/predicate-v2.js +145 -22
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +53 -3
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +3 -1
- package/dist/sdl.d.ts +3 -1
- package/dist/sdl.js +53 -3
- package/dist/sdl.js.map +1 -1
- package/package.json +1 -1
- package/dist/{definition-DjfC7Lll.d.cts → definition-B99BsOAO.d.cts} +4 -4
- package/dist/{definition-DjfC7Lll.d.ts → definition-B99BsOAO.d.ts} +4 -4
package/dist/predicate-v2.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ClaimKey, PredIr } from './ir.cjs';
|
|
2
2
|
import { P as PolicyExpression } from './policy-1iZbGnZZ.cjs';
|
|
3
3
|
import 'zod';
|
|
4
|
-
import './constants-
|
|
4
|
+
import './constants-Dou1RkTo.cjs';
|
|
5
5
|
|
|
6
6
|
declare const POLICY_COMPARE_OPS: readonly ["=", "!=", "<", "<=", ">", ">="];
|
|
7
7
|
type PolicyCompareOp = (typeof POLICY_COMPARE_OPS)[number];
|
|
@@ -50,6 +50,16 @@ type PolicyNode = {
|
|
|
50
50
|
kind: 'exists';
|
|
51
51
|
path: readonly string[];
|
|
52
52
|
where: PolicyNode;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* `visible(<link-path>)` — a related row exists AND the caller may read it:
|
|
56
|
+
* the final target's own read policy is compiled in as the inner predicate.
|
|
57
|
+
* Lets a write policy say "only on a parent you can see" without restating
|
|
58
|
+
* the parent's rule (and without drifting from it).
|
|
59
|
+
*/
|
|
60
|
+
| {
|
|
61
|
+
kind: 'visible';
|
|
62
|
+
path: readonly string[];
|
|
53
63
|
};
|
|
54
64
|
interface ParsedPolicy {
|
|
55
65
|
readonly source: string;
|
|
@@ -63,7 +73,7 @@ declare class PolicyParseError extends Error {
|
|
|
63
73
|
readonly name = "PolicyParseError";
|
|
64
74
|
constructor(message: string, position: number);
|
|
65
75
|
}
|
|
66
|
-
type PolicyCompileErrorCode = 'unknown-table' | 'unknown-column' | 'unknown-link' | 'not-to-one' | 'link-depth-exceeded' | 'unsupported-comparison' | 'invalid-null-comparison' | 'when-not-claims-only' | 'claim-type-mismatch';
|
|
76
|
+
type PolicyCompileErrorCode = 'unknown-table' | 'unknown-column' | 'unknown-link' | 'not-to-one' | 'link-depth-exceeded' | 'unsupported-comparison' | 'invalid-null-comparison' | 'when-not-claims-only' | 'claim-type-mismatch' | 'visible-unresolvable' | 'visible-cycle';
|
|
67
77
|
/**
|
|
68
78
|
* Compile-time failure — the parsed policy references something the table
|
|
69
79
|
* doesn't declare, or uses a shape the v2.0 IR cannot represent. Carries
|
|
@@ -96,6 +106,11 @@ declare function parsePolicy(source: string): ParsedPolicy;
|
|
|
96
106
|
* - variant `when` guards are claims-only: capability()/role() under
|
|
97
107
|
* and/or/not. (claim() comparisons in `when` are not representable in the
|
|
98
108
|
* v2.0 IR — a targeted error says so.)
|
|
109
|
+
* - `visible(<link-path>)` lowers to `exists(<link-path>, <target read policy>)`,
|
|
110
|
+
* compiled from the target's own read policy expression at the combined
|
|
111
|
+
* depth. A read policy that reaches itself again through visible() is a
|
|
112
|
+
* cycle and refused; a target with no read policy is readable to everyone
|
|
113
|
+
* who may use the namespace, so visibility reduces to existence.
|
|
99
114
|
*/
|
|
100
115
|
|
|
101
116
|
declare const POLICY_MAX_LINK_DEPTH = 3;
|
|
@@ -120,6 +135,19 @@ interface PolicyTableInfo {
|
|
|
120
135
|
/** `{ tables }` keyed by table name — TableMetaIr's record is assignable. */
|
|
121
136
|
interface PolicyResolver {
|
|
122
137
|
readonly tables: Record<string, PolicyTableInfo>;
|
|
138
|
+
/**
|
|
139
|
+
* A table's read policy expression, for `visible()`. Return `undefined` when
|
|
140
|
+
* the table has none (readable to every namespace user). A resolver without
|
|
141
|
+
* this callback cannot compile `visible()`: it is refused, never guessed.
|
|
142
|
+
*/
|
|
143
|
+
readonly readPolicy?: (table: string) => PolicyExpression | undefined;
|
|
144
|
+
/**
|
|
145
|
+
* The namespace that owns a table's policies, in a composed namespace. A
|
|
146
|
+
* visible() expansion of an imported table's read policy evaluates its
|
|
147
|
+
* capability() and role() checks in that namespace, not the importer's.
|
|
148
|
+
* Omitted (or undefined for a table) leaves them unscoped.
|
|
149
|
+
*/
|
|
150
|
+
readonly policyNamespace?: (table: string) => string | undefined;
|
|
123
151
|
}
|
|
124
152
|
interface CompilePredicateInput {
|
|
125
153
|
/** DSL source text, or an already-parsed policy. */
|
|
@@ -131,11 +159,20 @@ interface CompilePredicateInput {
|
|
|
131
159
|
declare function compilePredicateV2({ expression, table, resolver, maxLinkDepth }: CompilePredicateInput): PredIr;
|
|
132
160
|
/** Variant `when` guards may only test claims: capability()/role() under and/or/not. */
|
|
133
161
|
declare function assertClaimsOnlyWhen(parsed: ParsedPolicy): void;
|
|
162
|
+
/** Which of a table's policies is being compiled. */
|
|
163
|
+
type PolicyAccess = 'read' | 'write' | 'insert' | 'update';
|
|
134
164
|
interface CompilePolicyInput {
|
|
135
165
|
expression: PolicyExpression;
|
|
136
166
|
table: string;
|
|
137
167
|
resolver: PolicyResolver;
|
|
138
168
|
maxLinkDepth?: number;
|
|
169
|
+
/**
|
|
170
|
+
* Default 'read'. A read policy that reaches its own table through visible()
|
|
171
|
+
* is a cycle; a write policy that follows a self-link (a subtask written
|
|
172
|
+
* under a parent the caller can read) only expands the read policy, so it
|
|
173
|
+
* does not start the cycle check at its own table.
|
|
174
|
+
*/
|
|
175
|
+
access?: PolicyAccess;
|
|
139
176
|
}
|
|
140
177
|
/**
|
|
141
178
|
* Policy expression (string or `{default, variants}`) → one PredIr under §4c
|
|
@@ -146,6 +183,6 @@ interface CompilePolicyInput {
|
|
|
146
183
|
* Granting a role/capability only ever widens access; `allow: true`
|
|
147
184
|
* short-circuits its branch to the guard alone.
|
|
148
185
|
*/
|
|
149
|
-
declare function compilePolicy({ expression, table, resolver, maxLinkDepth }: CompilePolicyInput): PredIr;
|
|
186
|
+
declare function compilePolicy({ expression, table, resolver, maxLinkDepth, access }: CompilePolicyInput): PredIr;
|
|
150
187
|
|
|
151
|
-
export { type CompilePolicyInput, type CompilePredicateInput, POLICY_COMPARE_OPS, POLICY_MAX_LINK_DEPTH, type ParsedPolicy, type PolicyColumnInfo, type PolicyCompareOp, PolicyCompileError, type PolicyCompileErrorCode, type PolicyLinkInfo, type PolicyNode, type PolicyOperand, PolicyParseError, type PolicyResolver, type PolicyTableInfo, assertClaimsOnlyWhen, compilePolicy, compilePredicateV2, parsePolicy, walkPolicy };
|
|
188
|
+
export { type CompilePolicyInput, type CompilePredicateInput, POLICY_COMPARE_OPS, POLICY_MAX_LINK_DEPTH, type ParsedPolicy, type PolicyAccess, type PolicyColumnInfo, type PolicyCompareOp, PolicyCompileError, type PolicyCompileErrorCode, type PolicyLinkInfo, type PolicyNode, type PolicyOperand, PolicyParseError, type PolicyResolver, type PolicyTableInfo, assertClaimsOnlyWhen, compilePolicy, compilePredicateV2, parsePolicy, walkPolicy };
|
package/dist/predicate-v2.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ClaimKey, PredIr } from './ir.js';
|
|
2
2
|
import { P as PolicyExpression } from './policy-1iZbGnZZ.js';
|
|
3
3
|
import 'zod';
|
|
4
|
-
import './constants-
|
|
4
|
+
import './constants-Dou1RkTo.js';
|
|
5
5
|
|
|
6
6
|
declare const POLICY_COMPARE_OPS: readonly ["=", "!=", "<", "<=", ">", ">="];
|
|
7
7
|
type PolicyCompareOp = (typeof POLICY_COMPARE_OPS)[number];
|
|
@@ -50,6 +50,16 @@ type PolicyNode = {
|
|
|
50
50
|
kind: 'exists';
|
|
51
51
|
path: readonly string[];
|
|
52
52
|
where: PolicyNode;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* `visible(<link-path>)` — a related row exists AND the caller may read it:
|
|
56
|
+
* the final target's own read policy is compiled in as the inner predicate.
|
|
57
|
+
* Lets a write policy say "only on a parent you can see" without restating
|
|
58
|
+
* the parent's rule (and without drifting from it).
|
|
59
|
+
*/
|
|
60
|
+
| {
|
|
61
|
+
kind: 'visible';
|
|
62
|
+
path: readonly string[];
|
|
53
63
|
};
|
|
54
64
|
interface ParsedPolicy {
|
|
55
65
|
readonly source: string;
|
|
@@ -63,7 +73,7 @@ declare class PolicyParseError extends Error {
|
|
|
63
73
|
readonly name = "PolicyParseError";
|
|
64
74
|
constructor(message: string, position: number);
|
|
65
75
|
}
|
|
66
|
-
type PolicyCompileErrorCode = 'unknown-table' | 'unknown-column' | 'unknown-link' | 'not-to-one' | 'link-depth-exceeded' | 'unsupported-comparison' | 'invalid-null-comparison' | 'when-not-claims-only' | 'claim-type-mismatch';
|
|
76
|
+
type PolicyCompileErrorCode = 'unknown-table' | 'unknown-column' | 'unknown-link' | 'not-to-one' | 'link-depth-exceeded' | 'unsupported-comparison' | 'invalid-null-comparison' | 'when-not-claims-only' | 'claim-type-mismatch' | 'visible-unresolvable' | 'visible-cycle';
|
|
67
77
|
/**
|
|
68
78
|
* Compile-time failure — the parsed policy references something the table
|
|
69
79
|
* doesn't declare, or uses a shape the v2.0 IR cannot represent. Carries
|
|
@@ -96,6 +106,11 @@ declare function parsePolicy(source: string): ParsedPolicy;
|
|
|
96
106
|
* - variant `when` guards are claims-only: capability()/role() under
|
|
97
107
|
* and/or/not. (claim() comparisons in `when` are not representable in the
|
|
98
108
|
* v2.0 IR — a targeted error says so.)
|
|
109
|
+
* - `visible(<link-path>)` lowers to `exists(<link-path>, <target read policy>)`,
|
|
110
|
+
* compiled from the target's own read policy expression at the combined
|
|
111
|
+
* depth. A read policy that reaches itself again through visible() is a
|
|
112
|
+
* cycle and refused; a target with no read policy is readable to everyone
|
|
113
|
+
* who may use the namespace, so visibility reduces to existence.
|
|
99
114
|
*/
|
|
100
115
|
|
|
101
116
|
declare const POLICY_MAX_LINK_DEPTH = 3;
|
|
@@ -120,6 +135,19 @@ interface PolicyTableInfo {
|
|
|
120
135
|
/** `{ tables }` keyed by table name — TableMetaIr's record is assignable. */
|
|
121
136
|
interface PolicyResolver {
|
|
122
137
|
readonly tables: Record<string, PolicyTableInfo>;
|
|
138
|
+
/**
|
|
139
|
+
* A table's read policy expression, for `visible()`. Return `undefined` when
|
|
140
|
+
* the table has none (readable to every namespace user). A resolver without
|
|
141
|
+
* this callback cannot compile `visible()`: it is refused, never guessed.
|
|
142
|
+
*/
|
|
143
|
+
readonly readPolicy?: (table: string) => PolicyExpression | undefined;
|
|
144
|
+
/**
|
|
145
|
+
* The namespace that owns a table's policies, in a composed namespace. A
|
|
146
|
+
* visible() expansion of an imported table's read policy evaluates its
|
|
147
|
+
* capability() and role() checks in that namespace, not the importer's.
|
|
148
|
+
* Omitted (or undefined for a table) leaves them unscoped.
|
|
149
|
+
*/
|
|
150
|
+
readonly policyNamespace?: (table: string) => string | undefined;
|
|
123
151
|
}
|
|
124
152
|
interface CompilePredicateInput {
|
|
125
153
|
/** DSL source text, or an already-parsed policy. */
|
|
@@ -131,11 +159,20 @@ interface CompilePredicateInput {
|
|
|
131
159
|
declare function compilePredicateV2({ expression, table, resolver, maxLinkDepth }: CompilePredicateInput): PredIr;
|
|
132
160
|
/** Variant `when` guards may only test claims: capability()/role() under and/or/not. */
|
|
133
161
|
declare function assertClaimsOnlyWhen(parsed: ParsedPolicy): void;
|
|
162
|
+
/** Which of a table's policies is being compiled. */
|
|
163
|
+
type PolicyAccess = 'read' | 'write' | 'insert' | 'update';
|
|
134
164
|
interface CompilePolicyInput {
|
|
135
165
|
expression: PolicyExpression;
|
|
136
166
|
table: string;
|
|
137
167
|
resolver: PolicyResolver;
|
|
138
168
|
maxLinkDepth?: number;
|
|
169
|
+
/**
|
|
170
|
+
* Default 'read'. A read policy that reaches its own table through visible()
|
|
171
|
+
* is a cycle; a write policy that follows a self-link (a subtask written
|
|
172
|
+
* under a parent the caller can read) only expands the read policy, so it
|
|
173
|
+
* does not start the cycle check at its own table.
|
|
174
|
+
*/
|
|
175
|
+
access?: PolicyAccess;
|
|
139
176
|
}
|
|
140
177
|
/**
|
|
141
178
|
* Policy expression (string or `{default, variants}`) → one PredIr under §4c
|
|
@@ -146,6 +183,6 @@ interface CompilePolicyInput {
|
|
|
146
183
|
* Granting a role/capability only ever widens access; `allow: true`
|
|
147
184
|
* short-circuits its branch to the guard alone.
|
|
148
185
|
*/
|
|
149
|
-
declare function compilePolicy({ expression, table, resolver, maxLinkDepth }: CompilePolicyInput): PredIr;
|
|
186
|
+
declare function compilePolicy({ expression, table, resolver, maxLinkDepth, access }: CompilePolicyInput): PredIr;
|
|
150
187
|
|
|
151
|
-
export { type CompilePolicyInput, type CompilePredicateInput, POLICY_COMPARE_OPS, POLICY_MAX_LINK_DEPTH, type ParsedPolicy, type PolicyColumnInfo, type PolicyCompareOp, PolicyCompileError, type PolicyCompileErrorCode, type PolicyLinkInfo, type PolicyNode, type PolicyOperand, PolicyParseError, type PolicyResolver, type PolicyTableInfo, assertClaimsOnlyWhen, compilePolicy, compilePredicateV2, parsePolicy, walkPolicy };
|
|
188
|
+
export { type CompilePolicyInput, type CompilePredicateInput, POLICY_COMPARE_OPS, POLICY_MAX_LINK_DEPTH, type ParsedPolicy, type PolicyAccess, type PolicyColumnInfo, type PolicyCompareOp, PolicyCompileError, type PolicyCompileErrorCode, type PolicyLinkInfo, type PolicyNode, type PolicyOperand, PolicyParseError, type PolicyResolver, type PolicyTableInfo, assertClaimsOnlyWhen, compilePolicy, compilePredicateV2, parsePolicy, walkPolicy };
|
package/dist/predicate-v2.js
CHANGED
|
@@ -29,9 +29,11 @@ var PolicyCompileError = class extends Error {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
// src/constants.ts
|
|
32
|
+
var NAMESPACE_PATTERN = /^(?!(?:events|native_sources)$)[a-z][a-z0-9_]{0,40}$/;
|
|
32
33
|
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
33
34
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
34
35
|
var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
|
|
36
|
+
var MAX_PERSISTED_READ_ROWS = 50001;
|
|
35
37
|
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
|
|
36
38
|
var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
|
|
37
39
|
var CELL_PART_SUFFIX = {
|
|
@@ -381,6 +383,7 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
381
383
|
function validateSource({ scope, source, table }) {
|
|
382
384
|
const { fail } = scope;
|
|
383
385
|
if (source.kind === "etl" && table.source === void 0) fail(`etl source '${source.name}' must reference a synced table`);
|
|
386
|
+
if (source.kind === "etl" && table.primaryKey?.length === 0) fail(`etl source '${source.name}' cannot use an unkeyed synced table for resolved row identity`);
|
|
384
387
|
if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
|
|
385
388
|
fail(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
|
|
386
389
|
}
|
|
@@ -672,7 +675,7 @@ var canonicalTableSchema = z.object({
|
|
|
672
675
|
/** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
|
|
673
676
|
source: z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
|
|
674
677
|
columns: z.array(canonicalColumnSchema).default([]),
|
|
675
|
-
primaryKey: z.array(columnIdentifier).
|
|
678
|
+
primaryKey: z.array(columnIdentifier).max(8).optional(),
|
|
676
679
|
links: z.array(canonicalLinkSchema).default([]),
|
|
677
680
|
indexes: z.array(canonicalIndexSchema).default([]),
|
|
678
681
|
/** Omitted by old definitions, preserving their existing content hashes. */
|
|
@@ -708,6 +711,29 @@ var canonicalTableSchema = z.object({
|
|
|
708
711
|
* data plane. Omitted otherwise.
|
|
709
712
|
*/
|
|
710
713
|
materializes: identifier2.optional()
|
|
714
|
+
}).superRefine((table, ctx) => {
|
|
715
|
+
if (table.primaryKey?.length !== 0) return;
|
|
716
|
+
const fail = (message, path = ["primaryKey"]) => ctx.addIssue({ code: "custom", path, message: `unkeyed table '${table.table}' ${message}` });
|
|
717
|
+
if (table.source === void 0 || table.columns.length === 0) {
|
|
718
|
+
fail("requires a typed synced source");
|
|
719
|
+
}
|
|
720
|
+
if ([table.policies.write, table.policies.insert, table.policies.update].some((policy) => policy !== void 0)) {
|
|
721
|
+
fail("is read-only and cannot declare write policies", ["policies"]);
|
|
722
|
+
}
|
|
723
|
+
const ownedCollections = [table.indexes, table.actions, table.checks, table.foreignKeys, table.cowMutations];
|
|
724
|
+
const ownedMetadata = [table.cellMetadata, table.internal, table.materializes, table.renamedFrom];
|
|
725
|
+
if (ownedCollections.some((items) => items !== void 0 && items.length > 0) || ownedMetadata.some((value) => value !== void 0)) {
|
|
726
|
+
fail("cannot declare owned storage, constraints or identity operations");
|
|
727
|
+
}
|
|
728
|
+
for (const [index, column] of table.columns.entries()) {
|
|
729
|
+
const ownedColumnMetadata = [column.default, column.defaultFn, column.onUpdateFn, column.renamedFrom, column.cast, column.cell];
|
|
730
|
+
if (column.unique || ownedColumnMetadata.some((value) => value !== void 0)) {
|
|
731
|
+
fail("cannot declare column uniqueness, mutation defaults or migration metadata", ["columns", index]);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
for (const [index, link] of table.links.entries()) {
|
|
735
|
+
if (link.enforce || link.kind === "toMany") fail("cannot provide an identity for this link", ["links", index]);
|
|
736
|
+
}
|
|
711
737
|
});
|
|
712
738
|
var MAX_VIEW_QUERY = 1e5;
|
|
713
739
|
var MAX_RESOLVED_VIEW_QUERY = 5e6;
|
|
@@ -764,6 +790,26 @@ var canonicalViewSchema = z.object({
|
|
|
764
790
|
}
|
|
765
791
|
}
|
|
766
792
|
});
|
|
793
|
+
function validateUnkeyedLinkIdentities({ definition, ctx }) {
|
|
794
|
+
const tables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
795
|
+
const relations = [
|
|
796
|
+
...definition.tables.map((table, index) => ({ kind: "tables", index, links: table.links })),
|
|
797
|
+
...(definition.views ?? []).map((view, index) => ({ kind: "views", index, links: view.links }))
|
|
798
|
+
];
|
|
799
|
+
for (const relation of relations) {
|
|
800
|
+
for (const [linkIndex, link] of relation.links.entries()) {
|
|
801
|
+
if (link.kind !== "toOne" || link.namespace !== void 0 && link.namespace !== definition.namespace) continue;
|
|
802
|
+
const target = link.target === void 0 ? void 0 : tables.get(link.target);
|
|
803
|
+
if (target?.source !== void 0 && target.primaryKey?.length === 0) {
|
|
804
|
+
ctx.addIssue({
|
|
805
|
+
code: "custom",
|
|
806
|
+
path: [relation.kind, relation.index, "links", linkIndex],
|
|
807
|
+
message: `link '${link.name}' cannot use unkeyed table '${target.table}' as a to-one identity`
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
767
813
|
var CELL_PART_TYPES = {
|
|
768
814
|
at: "timestamptz",
|
|
769
815
|
by: "text",
|
|
@@ -827,7 +873,7 @@ var exportGateSchema = z.union([z.literal(true), z.object({ capability: z.string
|
|
|
827
873
|
var relationExportSchema = z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
|
|
828
874
|
var localDefinitionSchema = z.object({
|
|
829
875
|
definition_format: z.literal(CANONICAL_FORMAT),
|
|
830
|
-
namespace: identifier2.max(41),
|
|
876
|
+
namespace: identifier2.max(41).regex(NAMESPACE_PATTERN, "invalid or reserved ORM namespace"),
|
|
831
877
|
description: z.string().max(2e3).optional(),
|
|
832
878
|
enums: z.array(canonicalEnumSchema).default([]),
|
|
833
879
|
tables: z.array(canonicalTableSchema).min(0).max(500),
|
|
@@ -861,6 +907,7 @@ var localDefinitionSchema = z.object({
|
|
|
861
907
|
}
|
|
862
908
|
}
|
|
863
909
|
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
910
|
+
validateUnkeyedLinkIdentities({ definition, ctx });
|
|
864
911
|
const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
|
|
865
912
|
for (const name of Object.keys(definition.exports ?? {})) {
|
|
866
913
|
if (!relations.has(name)) ctx.addIssue({ code: "custom", path: ["exports", name], message: `export '${name}' is not a local object` });
|
|
@@ -1124,7 +1171,7 @@ var limitIrSchema = z.object({
|
|
|
1124
1171
|
variable: z.string().optional(),
|
|
1125
1172
|
value: z.number().int().positive().optional(),
|
|
1126
1173
|
default: z.number().int().positive(),
|
|
1127
|
-
max: z.number().int().positive()
|
|
1174
|
+
max: z.number().int().positive().max(MAX_PERSISTED_READ_ROWS)
|
|
1128
1175
|
});
|
|
1129
1176
|
var selectionIrSchema = z.lazy(
|
|
1130
1177
|
() => z.object({
|
|
@@ -1640,6 +1687,7 @@ var Parser2 = class {
|
|
|
1640
1687
|
return node;
|
|
1641
1688
|
}
|
|
1642
1689
|
if (this.peekKeyword("exists")) return this.parseExists();
|
|
1690
|
+
if (this.peekKeyword("visible") && this.tokens[this.index + 1]?.kind === "lparen") return this.parseVisible();
|
|
1643
1691
|
return this.parseComparison();
|
|
1644
1692
|
}
|
|
1645
1693
|
parseBooleanCall(fn) {
|
|
@@ -1659,6 +1707,13 @@ var Parser2 = class {
|
|
|
1659
1707
|
this.expect("rparen", "')' closing exists(...)");
|
|
1660
1708
|
return { kind: "exists", path, where };
|
|
1661
1709
|
}
|
|
1710
|
+
parseVisible() {
|
|
1711
|
+
this.advance();
|
|
1712
|
+
this.expect("lparen", "'(' after visible");
|
|
1713
|
+
const path = this.parsePath("a link path in visible(...)");
|
|
1714
|
+
this.expect("rparen", "')' closing visible(...)");
|
|
1715
|
+
return { kind: "visible", path };
|
|
1716
|
+
}
|
|
1662
1717
|
parseComparison() {
|
|
1663
1718
|
const left = this.parseOperand();
|
|
1664
1719
|
const operator = this.expect("operator", "a comparison operator");
|
|
@@ -1774,6 +1829,24 @@ function parsePolicy(source) {
|
|
|
1774
1829
|
|
|
1775
1830
|
// src/predicate-v2/compile.ts
|
|
1776
1831
|
var POLICY_MAX_LINK_DEPTH = 3;
|
|
1832
|
+
function scopeToNamespace(pred, namespace) {
|
|
1833
|
+
switch (pred.kind) {
|
|
1834
|
+
case "capability":
|
|
1835
|
+
case "role":
|
|
1836
|
+
return pred.namespace ? pred : { ...pred, namespace };
|
|
1837
|
+
case "and":
|
|
1838
|
+
case "or":
|
|
1839
|
+
return { ...pred, operands: pred.operands.map((operand) => scopeToNamespace(operand, namespace)) };
|
|
1840
|
+
case "not":
|
|
1841
|
+
return { ...pred, operand: scopeToNamespace(pred.operand, namespace) };
|
|
1842
|
+
case "exists":
|
|
1843
|
+
return { ...pred, where: scopeToNamespace(pred.where, namespace) };
|
|
1844
|
+
case "aggPredicate":
|
|
1845
|
+
return pred.where ? { ...pred, where: scopeToNamespace(pred.where, namespace) } : pred;
|
|
1846
|
+
default:
|
|
1847
|
+
return pred;
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1777
1850
|
var CMP_BY_OP = {
|
|
1778
1851
|
"=": "eq",
|
|
1779
1852
|
"!=": "neq",
|
|
@@ -1807,7 +1880,7 @@ function resolveLink(info, ref) {
|
|
|
1807
1880
|
function columnCandidates(info) {
|
|
1808
1881
|
return [...info.columns.map((c) => c.name), ...info.links.map((l) => l.name)];
|
|
1809
1882
|
}
|
|
1810
|
-
var PolicyCompiler = class {
|
|
1883
|
+
var PolicyCompiler = class _PolicyCompiler {
|
|
1811
1884
|
constructor(ctx) {
|
|
1812
1885
|
this.ctx = ctx;
|
|
1813
1886
|
}
|
|
@@ -1820,13 +1893,15 @@ var PolicyCompiler = class {
|
|
|
1820
1893
|
return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
|
|
1821
1894
|
case "not":
|
|
1822
1895
|
assertNoNegatedExternalClaim(node.operand);
|
|
1823
|
-
return { kind: "not", operand: this.compile(node.operand, table, depth) };
|
|
1896
|
+
return { kind: "not", operand: new _PolicyCompiler({ ...this.ctx, negated: true }).compile(node.operand, table, depth) };
|
|
1824
1897
|
case "capability":
|
|
1825
1898
|
return { kind: "capability", key: node.key };
|
|
1826
1899
|
case "role":
|
|
1827
1900
|
return { kind: "role", key: node.key };
|
|
1828
1901
|
case "exists":
|
|
1829
1902
|
return this.compileExists(node.path, node.where, table, depth);
|
|
1903
|
+
case "visible":
|
|
1904
|
+
return this.compileVisible(node.path, table, depth);
|
|
1830
1905
|
case "compare":
|
|
1831
1906
|
return this.compileCompare(node, table, depth);
|
|
1832
1907
|
}
|
|
@@ -1840,6 +1915,36 @@ var PolicyCompiler = class {
|
|
|
1840
1915
|
}
|
|
1841
1916
|
return pred;
|
|
1842
1917
|
}
|
|
1918
|
+
/** `visible(path)` → `exists(path, <final target's read policy>)`. */
|
|
1919
|
+
compileVisible(path, table, depth) {
|
|
1920
|
+
const { links, finalTable } = this.resolveLinkPath(path, table, depth, { toOneOnly: false });
|
|
1921
|
+
const readPolicy = this.ctx.resolver.readPolicy;
|
|
1922
|
+
if (!readPolicy) {
|
|
1923
|
+
throw new PolicyCompileError(
|
|
1924
|
+
`visible(${path.join(".")}) needs the target's read policy, which this compile context does not provide`,
|
|
1925
|
+
"visible-unresolvable"
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
if (this.ctx.visiting.includes(finalTable)) {
|
|
1929
|
+
throw new PolicyCompileError(
|
|
1930
|
+
`visible(${path.join(".")}) on '${table}' reaches '${finalTable}' whose read policy is already being expanded (${[...this.ctx.visiting, finalTable].join(" \u2192 ")})`,
|
|
1931
|
+
"visible-cycle"
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
const expression = readPolicy(finalTable);
|
|
1935
|
+
let pred = expression === void 0 ? { kind: "const", value: true } : compilePolicyAt({
|
|
1936
|
+
expression,
|
|
1937
|
+
table: finalTable,
|
|
1938
|
+
ctx: { ...this.ctx, visiting: [...this.ctx.visiting, finalTable] },
|
|
1939
|
+
depth: depth + links.length
|
|
1940
|
+
});
|
|
1941
|
+
const owner = this.ctx.resolver.policyNamespace?.(finalTable);
|
|
1942
|
+
if (owner) pred = scopeToNamespace(pred, owner);
|
|
1943
|
+
for (let i = links.length - 1; i >= 0; i -= 1) {
|
|
1944
|
+
pred = { kind: "exists", link: links[i].name, where: pred };
|
|
1945
|
+
}
|
|
1946
|
+
return pred;
|
|
1947
|
+
}
|
|
1843
1948
|
resolveLinkPath(path, table, depth, { toOneOnly }) {
|
|
1844
1949
|
const links = [];
|
|
1845
1950
|
let current = table;
|
|
@@ -1900,6 +2005,7 @@ var PolicyCompiler = class {
|
|
|
1900
2005
|
);
|
|
1901
2006
|
}
|
|
1902
2007
|
if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
|
|
2008
|
+
if (this.ctx.negated) throw negatedExternalClaim(valueSide.key);
|
|
1903
2009
|
assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
|
|
1904
2010
|
}
|
|
1905
2011
|
let cmp = this.buildCmp(op, column, valueSide);
|
|
@@ -1937,22 +2043,30 @@ function assertExternalClaimComparison({ op, key, column }) {
|
|
|
1937
2043
|
);
|
|
1938
2044
|
}
|
|
1939
2045
|
}
|
|
2046
|
+
function negatedExternalClaim(key) {
|
|
2047
|
+
return new PolicyCompileError(
|
|
2048
|
+
`claim('${key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
|
|
2049
|
+
"unsupported-comparison"
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
1940
2052
|
function assertNoNegatedExternalClaim(operand) {
|
|
1941
2053
|
walkPolicy(operand, (node) => {
|
|
1942
2054
|
if (node.kind !== "compare") return;
|
|
1943
2055
|
const claim = [node.left, node.right].find((side) => side.kind === "claim" && isExternalPolicyClaimKey(side.key));
|
|
1944
|
-
if (claim?.kind === "claim")
|
|
1945
|
-
throw new PolicyCompileError(
|
|
1946
|
-
`claim('${claim.key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
|
|
1947
|
-
"unsupported-comparison"
|
|
1948
|
-
);
|
|
1949
|
-
}
|
|
2056
|
+
if (claim?.kind === "claim") throw negatedExternalClaim(claim.key);
|
|
1950
2057
|
});
|
|
1951
2058
|
}
|
|
1952
2059
|
function compilePredicateV2({ expression, table, resolver, maxLinkDepth }) {
|
|
2060
|
+
return compilePredicateAt({
|
|
2061
|
+
expression,
|
|
2062
|
+
table,
|
|
2063
|
+
depth: 0,
|
|
2064
|
+
ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: [table] }
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
function compilePredicateAt({ expression, table, ctx, depth }) {
|
|
1953
2068
|
const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
|
|
1954
|
-
|
|
1955
|
-
return compiler.compile(parsed.ast, table, 0);
|
|
2069
|
+
return new PolicyCompiler(ctx).compile(parsed.ast, table, depth);
|
|
1956
2070
|
}
|
|
1957
2071
|
function assertClaimsOnlyWhen(parsed) {
|
|
1958
2072
|
walkPolicy(parsed.ast, (node) => {
|
|
@@ -1969,25 +2083,34 @@ function assertClaimsOnlyWhen(parsed) {
|
|
|
1969
2083
|
"when-not-claims-only"
|
|
1970
2084
|
);
|
|
1971
2085
|
}
|
|
1972
|
-
if (node.kind === "exists") {
|
|
2086
|
+
if (node.kind === "exists" || node.kind === "visible") {
|
|
1973
2087
|
throw new PolicyCompileError(
|
|
1974
|
-
|
|
2088
|
+
`variant 'when' guards are claims-only \u2014 ${node.kind}() is not allowed`,
|
|
1975
2089
|
"when-not-claims-only"
|
|
1976
2090
|
);
|
|
1977
2091
|
}
|
|
1978
2092
|
});
|
|
1979
2093
|
}
|
|
1980
|
-
function
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
2094
|
+
function initialVisiting({ table, access }) {
|
|
2095
|
+
return (access ?? "read") === "read" ? [table] : [];
|
|
2096
|
+
}
|
|
2097
|
+
function compilePolicy({ expression, table, resolver, maxLinkDepth, access }) {
|
|
2098
|
+
return compilePolicyAt({
|
|
2099
|
+
expression,
|
|
2100
|
+
table,
|
|
2101
|
+
depth: 0,
|
|
2102
|
+
ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: initialVisiting({ table, access }) }
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
function compilePolicyAt({ expression, table, ctx, depth }) {
|
|
2106
|
+
if (typeof expression === "string") return compilePredicateAt({ expression, table, ctx, depth });
|
|
2107
|
+
const defaultPred = compilePredicateAt({ expression: expression.default, table, ctx, depth });
|
|
1985
2108
|
if (expression.variants.length === 0) return defaultPred;
|
|
1986
2109
|
const branches = expression.variants.map((variant) => {
|
|
1987
2110
|
const parsedWhen = parsePolicy(variant.when);
|
|
1988
2111
|
assertClaimsOnlyWhen(parsedWhen);
|
|
1989
|
-
const when =
|
|
1990
|
-
const grant = variant.allow === true ? { kind: "const", value: true } :
|
|
2112
|
+
const when = compilePredicateAt({ expression: parsedWhen, table, ctx, depth });
|
|
2113
|
+
const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateAt({ expression: variant.policy, table, ctx, depth });
|
|
1991
2114
|
return { kind: "and", operands: [when, grant] };
|
|
1992
2115
|
});
|
|
1993
2116
|
return { kind: "or", operands: [defaultPred, ...branches] };
|