@sequenceholdings/orm 2.1.1 → 2.3.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/README.md +3 -1
- package/dist/authoring.cjs +284 -99
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +31 -8
- package/dist/authoring.d.ts +31 -8
- package/dist/authoring.js +282 -100
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +51 -3
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +838 -12
- package/dist/canonical.d.ts +838 -12
- package/dist/canonical.js +47 -4
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +5876 -4198
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +5876 -4198
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs +49 -32
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +49 -6
- package/dist/client.d.ts +49 -6
- package/dist/client.js +49 -32
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +1275 -264
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +57 -3
- package/dist/compiler.d.ts +57 -3
- package/dist/compiler.js +1268 -265
- package/dist/compiler.js.map +1 -1
- package/dist/{constants-YoFGjIBM.d.cts → constants-C6EG6Wq7.d.cts} +1 -1
- package/dist/{constants-YoFGjIBM.d.ts → constants-C6EG6Wq7.d.ts} +1 -1
- package/dist/definition-DjfC7Lll.d.cts +91 -0
- package/dist/definition-DjfC7Lll.d.ts +91 -0
- package/dist/dynamic-input.cjs +2 -1
- package/dist/dynamic-input.cjs.map +1 -1
- package/dist/dynamic-input.d.cts +9 -2
- package/dist/dynamic-input.d.ts +9 -2
- package/dist/dynamic-input.js +2 -1
- package/dist/dynamic-input.js.map +1 -1
- package/dist/index.cjs +1103 -322
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +479 -19
- package/dist/index.d.ts +479 -19
- package/dist/index.js +1090 -323
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +93 -11
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +226 -15
- package/dist/ir.d.ts +226 -15
- package/dist/ir.js +88 -12
- package/dist/ir.js.map +1 -1
- package/dist/predicate-v2.cjs +116 -10
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.d.cts +11 -3
- package/dist/predicate-v2.d.ts +11 -3
- package/dist/predicate-v2.js +116 -10
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +158 -22
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +8 -1
- package/dist/sdl.d.ts +8 -1
- package/dist/sdl.js +158 -22
- package/dist/sdl.js.map +1 -1
- package/package.json +5 -4
- package/src/codegen-gql/server-fns.cjs +2 -2
- package/templates/orm-namespace/AGENTS.md +10 -0
- package/templates/orm-namespace/CLAUDE.md +1 -84
- package/templates/orm-namespace/package.json +1 -1
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-C6EG6Wq7.cjs';
|
|
5
5
|
|
|
6
6
|
declare const POLICY_COMPARE_OPS: readonly ["=", "!=", "<", "<=", ">", ">="];
|
|
7
7
|
type PolicyCompareOp = (typeof POLICY_COMPARE_OPS)[number];
|
|
@@ -11,7 +11,7 @@ type PolicyOperand =
|
|
|
11
11
|
kind: 'path';
|
|
12
12
|
segments: readonly string[];
|
|
13
13
|
}
|
|
14
|
-
/** `claim('sub')` / `claim('org')`
|
|
14
|
+
/** `claim('sub')` / `claim('org')` / `claim('ext_subject')` / `claim('ext_email')` — the scalar keys in CLAIMS_CONTRACT. */
|
|
15
15
|
| {
|
|
16
16
|
kind: 'claim';
|
|
17
17
|
key: ClaimKey;
|
|
@@ -63,7 +63,7 @@ declare class PolicyParseError extends Error {
|
|
|
63
63
|
readonly name = "PolicyParseError";
|
|
64
64
|
constructor(message: string, position: number);
|
|
65
65
|
}
|
|
66
|
-
type PolicyCompileErrorCode = 'unknown-table' | 'unknown-column' | 'unknown-link' | 'not-to-one' | 'link-depth-exceeded' | 'unsupported-comparison' | 'invalid-null-comparison' | 'when-not-claims-only';
|
|
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';
|
|
67
67
|
/**
|
|
68
68
|
* Compile-time failure — the parsed policy references something the table
|
|
69
69
|
* doesn't declare, or uses a shape the v2.0 IR cannot represent. Carries
|
|
@@ -90,6 +90,9 @@ declare function parsePolicy(source: string): ParsedPolicy;
|
|
|
90
90
|
* - a comparison must reference exactly ONE column side (column↔column and
|
|
91
91
|
* claims↔literal comparisons have no PredIr shape in v2.0);
|
|
92
92
|
* - `col = null` / `col != null` normalize to the isNull operator;
|
|
93
|
+
* - verified-borrower claims (`claim('ext_subject')`, `claim('ext_email')`)
|
|
94
|
+
* are text: they compare to a text column with `=` only, never under
|
|
95
|
+
* not(), so no negation, ordering or pattern can widen a borrower's rows;
|
|
93
96
|
* - variant `when` guards are claims-only: capability()/role() under
|
|
94
97
|
* and/or/not. (claim() comparisons in `when` are not representable in the
|
|
95
98
|
* v2.0 IR — a targeted error says so.)
|
|
@@ -99,6 +102,11 @@ declare const POLICY_MAX_LINK_DEPTH = 3;
|
|
|
99
102
|
interface PolicyColumnInfo {
|
|
100
103
|
readonly name: string;
|
|
101
104
|
readonly columnName: string;
|
|
105
|
+
/**
|
|
106
|
+
* The column's IR type when the resolver knows it (TableMetaIr always does).
|
|
107
|
+
* Required for a verified-borrower claim comparison: unknown is refused.
|
|
108
|
+
*/
|
|
109
|
+
readonly type?: unknown;
|
|
102
110
|
}
|
|
103
111
|
interface PolicyLinkInfo {
|
|
104
112
|
readonly name: string;
|
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-C6EG6Wq7.js';
|
|
5
5
|
|
|
6
6
|
declare const POLICY_COMPARE_OPS: readonly ["=", "!=", "<", "<=", ">", ">="];
|
|
7
7
|
type PolicyCompareOp = (typeof POLICY_COMPARE_OPS)[number];
|
|
@@ -11,7 +11,7 @@ type PolicyOperand =
|
|
|
11
11
|
kind: 'path';
|
|
12
12
|
segments: readonly string[];
|
|
13
13
|
}
|
|
14
|
-
/** `claim('sub')` / `claim('org')`
|
|
14
|
+
/** `claim('sub')` / `claim('org')` / `claim('ext_subject')` / `claim('ext_email')` — the scalar keys in CLAIMS_CONTRACT. */
|
|
15
15
|
| {
|
|
16
16
|
kind: 'claim';
|
|
17
17
|
key: ClaimKey;
|
|
@@ -63,7 +63,7 @@ declare class PolicyParseError extends Error {
|
|
|
63
63
|
readonly name = "PolicyParseError";
|
|
64
64
|
constructor(message: string, position: number);
|
|
65
65
|
}
|
|
66
|
-
type PolicyCompileErrorCode = 'unknown-table' | 'unknown-column' | 'unknown-link' | 'not-to-one' | 'link-depth-exceeded' | 'unsupported-comparison' | 'invalid-null-comparison' | 'when-not-claims-only';
|
|
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';
|
|
67
67
|
/**
|
|
68
68
|
* Compile-time failure — the parsed policy references something the table
|
|
69
69
|
* doesn't declare, or uses a shape the v2.0 IR cannot represent. Carries
|
|
@@ -90,6 +90,9 @@ declare function parsePolicy(source: string): ParsedPolicy;
|
|
|
90
90
|
* - a comparison must reference exactly ONE column side (column↔column and
|
|
91
91
|
* claims↔literal comparisons have no PredIr shape in v2.0);
|
|
92
92
|
* - `col = null` / `col != null` normalize to the isNull operator;
|
|
93
|
+
* - verified-borrower claims (`claim('ext_subject')`, `claim('ext_email')`)
|
|
94
|
+
* are text: they compare to a text column with `=` only, never under
|
|
95
|
+
* not(), so no negation, ordering or pattern can widen a borrower's rows;
|
|
93
96
|
* - variant `when` guards are claims-only: capability()/role() under
|
|
94
97
|
* and/or/not. (claim() comparisons in `when` are not representable in the
|
|
95
98
|
* v2.0 IR — a targeted error says so.)
|
|
@@ -99,6 +102,11 @@ declare const POLICY_MAX_LINK_DEPTH = 3;
|
|
|
99
102
|
interface PolicyColumnInfo {
|
|
100
103
|
readonly name: string;
|
|
101
104
|
readonly columnName: string;
|
|
105
|
+
/**
|
|
106
|
+
* The column's IR type when the resolver knows it (TableMetaIr always does).
|
|
107
|
+
* Required for a verified-borrower claim comparison: unknown is refused.
|
|
108
|
+
*/
|
|
109
|
+
readonly type?: unknown;
|
|
102
110
|
}
|
|
103
111
|
interface PolicyLinkInfo {
|
|
104
112
|
readonly name: string;
|
package/dist/predicate-v2.js
CHANGED
|
@@ -32,6 +32,7 @@ var PolicyCompileError = class extends Error {
|
|
|
32
32
|
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
33
33
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
34
34
|
var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
|
|
35
|
+
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
|
|
35
36
|
var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
|
|
36
37
|
var CELL_PART_SUFFIX = {
|
|
37
38
|
at: "__at",
|
|
@@ -561,6 +562,8 @@ var canonicalLinkSchema = z.object({
|
|
|
561
562
|
kind: z.enum(["toOne", "toMany", "toPlatformUser"]),
|
|
562
563
|
/** Target table name (namespace-local). Absent for toPlatformUser. */
|
|
563
564
|
target: identifier2.optional(),
|
|
565
|
+
/** Owning namespace of an imported target; omitted for local links. */
|
|
566
|
+
namespace: identifier2.max(41).optional(),
|
|
564
567
|
/** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
|
|
565
568
|
on: columnIdentifier,
|
|
566
569
|
/** Referenced column on the target (default: target's PK / platform user `sub`). */
|
|
@@ -662,11 +665,12 @@ var canonicalForeignKeySchema = z.object({
|
|
|
662
665
|
}).strict();
|
|
663
666
|
var canonicalTableSchema = z.object({
|
|
664
667
|
table: identifier2,
|
|
668
|
+
displayName: z.string().trim().min(1).max(120).optional(),
|
|
665
669
|
description: z.string().max(2e3).optional(),
|
|
666
670
|
/** Table-level rename hint (new in v2). */
|
|
667
671
|
renamedFrom: identifier2.optional(),
|
|
668
|
-
/** Synced/reflected read-only tables (`source:
|
|
669
|
-
source: z.string().regex(
|
|
672
|
+
/** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
|
|
673
|
+
source: z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
|
|
670
674
|
columns: z.array(canonicalColumnSchema).default([]),
|
|
671
675
|
primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
|
|
672
676
|
links: z.array(canonicalLinkSchema).default([]),
|
|
@@ -819,7 +823,9 @@ function validateCellMetadata(table, index, ctx) {
|
|
|
819
823
|
}
|
|
820
824
|
}
|
|
821
825
|
}
|
|
822
|
-
z.object({
|
|
826
|
+
var exportGateSchema = z.union([z.literal(true), z.object({ capability: z.string().min(1) }).strict(), z.object({ role: z.string().min(1) }).strict()]);
|
|
827
|
+
var relationExportSchema = z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
|
|
828
|
+
var localDefinitionSchema = z.object({
|
|
823
829
|
definition_format: z.literal(CANONICAL_FORMAT),
|
|
824
830
|
namespace: identifier2.max(41),
|
|
825
831
|
description: z.string().max(2e3).optional(),
|
|
@@ -827,6 +833,8 @@ z.object({
|
|
|
827
833
|
tables: z.array(canonicalTableSchema).min(0).max(500),
|
|
828
834
|
/** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
|
|
829
835
|
views: z.array(canonicalViewSchema).max(500).optional(),
|
|
836
|
+
/** Target-owned cross-namespace access contract, using existing namespace grants. */
|
|
837
|
+
exports: z.record(identifier2, relationExportSchema).optional(),
|
|
830
838
|
/** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
|
|
831
839
|
capabilities: z.object({
|
|
832
840
|
roles: z.unknown().optional(),
|
|
@@ -853,7 +861,16 @@ z.object({
|
|
|
853
861
|
}
|
|
854
862
|
}
|
|
855
863
|
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
864
|
+
const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
|
|
865
|
+
for (const name of Object.keys(definition.exports ?? {})) {
|
|
866
|
+
if (!relations.has(name)) ctx.addIssue({ code: "custom", path: ["exports", name], message: `export '${name}' is not a local object` });
|
|
867
|
+
}
|
|
856
868
|
for (const [tableIndex, table] of definition.tables.entries()) {
|
|
869
|
+
for (const [linkIndex, link] of table.links.entries()) {
|
|
870
|
+
if (link.enforce && link.namespace !== void 0 && link.namespace !== definition.namespace) {
|
|
871
|
+
ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", linkIndex], message: "physical foreign keys require a local target; use an ORM link without enforce" });
|
|
872
|
+
}
|
|
873
|
+
}
|
|
857
874
|
const names = /* @__PURE__ */ new Set([
|
|
858
875
|
...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
|
|
859
876
|
...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
|
|
@@ -1023,23 +1040,36 @@ z.object({
|
|
|
1023
1040
|
};
|
|
1024
1041
|
for (const view of views) visit(view.view, []);
|
|
1025
1042
|
});
|
|
1043
|
+
localDefinitionSchema.safeExtend({
|
|
1044
|
+
/** Flattened, pinned snapshots. Imported relations never participate in this namespace's DDL. */
|
|
1045
|
+
imports: z.array(localDefinitionSchema).max(32).optional()
|
|
1046
|
+
});
|
|
1026
1047
|
|
|
1027
1048
|
// src/ir/index.ts
|
|
1028
|
-
var IR_VERSION = 2;
|
|
1029
1049
|
var LEGACY_IR_VERSION = 1;
|
|
1030
|
-
var
|
|
1050
|
+
var GUARDED_IR_VERSION = 2;
|
|
1051
|
+
var COMPOSED_IR_VERSION = 3;
|
|
1052
|
+
var SQL_IR_VERSION = 4;
|
|
1053
|
+
var RUNTIME_SQL_IR_VERSION = 5;
|
|
1054
|
+
var irVersionSchema = z.union([z.literal(LEGACY_IR_VERSION), z.literal(GUARDED_IR_VERSION), z.literal(COMPOSED_IR_VERSION), z.literal(SQL_IR_VERSION), z.literal(RUNTIME_SQL_IR_VERSION)]);
|
|
1031
1055
|
z.object({ irVersion: irVersionSchema });
|
|
1032
1056
|
var CLAIMS_CONTRACT = {
|
|
1033
1057
|
sub: "string",
|
|
1034
1058
|
org: "string?",
|
|
1059
|
+
ext_subject: "string?",
|
|
1060
|
+
ext_email: "string?",
|
|
1035
1061
|
roles: "string[]",
|
|
1036
1062
|
capabilities: "string[]"
|
|
1037
1063
|
};
|
|
1064
|
+
var EXTERNAL_POLICY_CLAIM_KEYS = ["ext_subject", "ext_email"];
|
|
1065
|
+
function isExternalPolicyClaimKey(key) {
|
|
1066
|
+
return EXTERNAL_POLICY_CLAIM_KEYS.includes(key);
|
|
1067
|
+
}
|
|
1038
1068
|
var scalarLiteral = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
|
1039
1069
|
var valueIrSchema = z.discriminatedUnion("kind", [
|
|
1040
1070
|
z.object({ kind: z.literal("literal"), value: z.union([scalarLiteral, z.array(scalarLiteral)]) }),
|
|
1041
1071
|
z.object({ kind: z.literal("variable"), name: z.string() }),
|
|
1042
|
-
z.object({ kind: z.literal("claim"), key: z.enum(["sub", "org"]) })
|
|
1072
|
+
z.object({ kind: z.literal("claim"), key: z.enum(["sub", "org", ...EXTERNAL_POLICY_CLAIM_KEYS]) })
|
|
1043
1073
|
]);
|
|
1044
1074
|
var CMP_OPS = [
|
|
1045
1075
|
"eq",
|
|
@@ -1071,8 +1101,8 @@ var predIrSchema = z.lazy(
|
|
|
1071
1101
|
value: z.number(),
|
|
1072
1102
|
where: predIrSchema.optional()
|
|
1073
1103
|
}),
|
|
1074
|
-
z.object({ kind: z.literal("capability"), key: z.string() }),
|
|
1075
|
-
z.object({ kind: z.literal("role"), key: z.string() }),
|
|
1104
|
+
z.object({ kind: z.literal("capability"), key: z.string(), namespace: z.string().optional() }),
|
|
1105
|
+
z.object({ kind: z.literal("role"), key: z.string(), namespace: z.string().optional() }),
|
|
1076
1106
|
z.object({ kind: z.literal("dynamicFilter"), slot: z.string() }),
|
|
1077
1107
|
z.object({ kind: z.literal("const"), value: z.boolean() })
|
|
1078
1108
|
])
|
|
@@ -1299,7 +1329,31 @@ var variableIrSchema = z.object({
|
|
|
1299
1329
|
/** Absent = no default declared; see {@link variableDefaultIrSchema}. */
|
|
1300
1330
|
defaultValue: variableDefaultIrSchema.optional()
|
|
1301
1331
|
});
|
|
1332
|
+
var sqlColumnSchema = z.object({ name: z.string(), type: z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]), nullable: z.boolean() });
|
|
1302
1333
|
var compiledOperationSchema = z.object({
|
|
1334
|
+
language: z.literal("sql").optional(),
|
|
1335
|
+
/** Server-compiled object catalog and permission ceiling, never caller SQL. */
|
|
1336
|
+
sqlQuery: z.object({
|
|
1337
|
+
catalog: z.object({
|
|
1338
|
+
identity: z.string(),
|
|
1339
|
+
objects: z.array(z.object({
|
|
1340
|
+
ref: z.string(),
|
|
1341
|
+
table: z.string(),
|
|
1342
|
+
columns: z.array(sqlColumnSchema.extend({ type: z.string() })).min(1),
|
|
1343
|
+
scan: z.string().min(1),
|
|
1344
|
+
read: z.array(z.string()).min(1)
|
|
1345
|
+
}).strict()).min(1).max(256)
|
|
1346
|
+
}).strict(),
|
|
1347
|
+
read: z.array(z.string()).min(1).max(256),
|
|
1348
|
+
maxRows: z.number().int().min(1).max(1e4)
|
|
1349
|
+
}).strict().optional(),
|
|
1350
|
+
sql: z.object({
|
|
1351
|
+
text: z.string().min(1).max(1048576),
|
|
1352
|
+
columns: z.array(sqlColumnSchema).min(1).max(256),
|
|
1353
|
+
parameters: z.array(z.string()).max(128),
|
|
1354
|
+
read: z.array(z.string()).min(1).max(256),
|
|
1355
|
+
maxRows: z.number().int().min(1).max(1e4)
|
|
1356
|
+
}).strict().optional(),
|
|
1303
1357
|
irVersion: irVersionSchema,
|
|
1304
1358
|
name: z.string(),
|
|
1305
1359
|
opHash: z.string().regex(/^sha256:[0-9a-f]{64}$/),
|
|
@@ -1311,8 +1365,16 @@ var compiledOperationSchema = z.object({
|
|
|
1311
1365
|
variables: z.array(variableIrSchema),
|
|
1312
1366
|
/** Every table the op touches — data-token scoping + audit. */
|
|
1313
1367
|
tableSet: z.array(z.string()).min(1),
|
|
1314
|
-
roots: z.array(rootIrSchema)
|
|
1368
|
+
roots: z.array(rootIrSchema)
|
|
1315
1369
|
}).superRefine((operation, ctx) => {
|
|
1370
|
+
if (operation.language === "sql") {
|
|
1371
|
+
const validPlan = operation.sqlQuery ? !operation.sql && operation.irVersion === RUNTIME_SQL_IR_VERSION : !!operation.sql && operation.irVersion === SQL_IR_VERSION;
|
|
1372
|
+
if (!validPlan || operation.kind !== "query" || operation.roots.length !== 0 || !operation.requires?.capability) {
|
|
1373
|
+
ctx.addIssue({ code: "custom", message: "SQL operations require a query-only v4 plan or v5 runtime contract, capability, and no GraphQL roots" });
|
|
1374
|
+
}
|
|
1375
|
+
} else if (operation.sql || operation.sqlQuery || operation.roots.length === 0) {
|
|
1376
|
+
ctx.addIssue({ code: "custom", message: "GraphQL operations require roots and cannot carry SQL" });
|
|
1377
|
+
}
|
|
1316
1378
|
for (const [index, root] of operation.roots.entries()) {
|
|
1317
1379
|
if (root.kind !== "mutation" || root.assertAffectedRows === void 0) continue;
|
|
1318
1380
|
if (operation.irVersion < 2 || operation.kind !== "mutation" || root.includeIf !== void 0) {
|
|
@@ -1359,11 +1421,16 @@ var resolvedMetaIrSchema = z.object({
|
|
|
1359
1421
|
});
|
|
1360
1422
|
var tableMetaIrSchema = z.object({
|
|
1361
1423
|
table: z.string(),
|
|
1424
|
+
namespace: z.string().optional(),
|
|
1425
|
+
dependencies: z.array(z.string()).optional(),
|
|
1426
|
+
foreignReferences: z.array(z.object({ column: z.string(), target: z.string(), targetColumn: z.string() })).optional(),
|
|
1427
|
+
exportRead: z.union([z.literal(true), z.object({ capability: z.string() }), z.object({ role: z.string() })]).optional(),
|
|
1428
|
+
exportWrite: z.union([z.literal(true), z.object({ capability: z.string() }), z.object({ role: z.string() })]).optional(),
|
|
1362
1429
|
schema: z.string(),
|
|
1363
1430
|
typename: z.string(),
|
|
1364
1431
|
/**
|
|
1365
1432
|
* True when statements over this relation evaluate a claims-gated synced
|
|
1366
|
-
* view: synced twins (`source:
|
|
1433
|
+
* view: synced twins (`source: schema.table`) materialize as views whose baked
|
|
1367
1434
|
* policy reads `current_setting('request.jwt.claims')`, and namespace views
|
|
1368
1435
|
* inherit the gate transitively through their declared dependencies. The
|
|
1369
1436
|
* executor must stamp that GUC (transaction-scoped) before any statement
|
|
@@ -1422,10 +1489,18 @@ z.object({
|
|
|
1422
1489
|
irVersion: irVersionSchema,
|
|
1423
1490
|
namespace: z.string(),
|
|
1424
1491
|
schemaHash: z.string(),
|
|
1492
|
+
ownSchemaHash: z.string().optional(),
|
|
1493
|
+
dependencies: z.record(z.string(), z.string()).optional(),
|
|
1494
|
+
readPolicies: z.record(z.string(), predIrSchema).optional(),
|
|
1425
1495
|
opsHash: z.string(),
|
|
1496
|
+
/** Apply-observed synced objects without a serving backing; not part of the authored schema hash. */
|
|
1497
|
+
unbackedTables: z.array(z.string()).optional(),
|
|
1426
1498
|
tables: z.record(z.string(), tableMetaIrSchema),
|
|
1427
1499
|
operations: z.record(z.string(), compiledOperationSchema)
|
|
1428
1500
|
}).superRefine((artifact, ctx) => {
|
|
1501
|
+
if (Object.keys(artifact.dependencies ?? {}).length > 0 && artifact.irVersion < COMPOSED_IR_VERSION) {
|
|
1502
|
+
ctx.addIssue({ code: "custom", path: ["irVersion"], message: "cross-namespace dependencies require IR v3" });
|
|
1503
|
+
}
|
|
1429
1504
|
for (const [name, operation] of Object.entries(artifact.operations)) {
|
|
1430
1505
|
if (operation.irVersion > artifact.irVersion) {
|
|
1431
1506
|
ctx.addIssue({ code: "custom", path: ["operations", name, "irVersion"], message: "operation requires a newer IR version than its artifact" });
|
|
@@ -1744,6 +1819,7 @@ var PolicyCompiler = class {
|
|
|
1744
1819
|
case "or":
|
|
1745
1820
|
return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
|
|
1746
1821
|
case "not":
|
|
1822
|
+
assertNoNegatedExternalClaim(node.operand);
|
|
1747
1823
|
return { kind: "not", operand: this.compile(node.operand, table, depth) };
|
|
1748
1824
|
case "capability":
|
|
1749
1825
|
return { kind: "capability", key: node.key };
|
|
@@ -1823,6 +1899,9 @@ var PolicyCompiler = class {
|
|
|
1823
1899
|
columnCandidates(info)
|
|
1824
1900
|
);
|
|
1825
1901
|
}
|
|
1902
|
+
if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
|
|
1903
|
+
assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
|
|
1904
|
+
}
|
|
1826
1905
|
let cmp = this.buildCmp(op, column, valueSide);
|
|
1827
1906
|
for (let i = links.length - 1; i >= 0; i -= 1) {
|
|
1828
1907
|
cmp = { kind: "exists", link: links[i].name, where: cmp };
|
|
@@ -1843,6 +1922,33 @@ var PolicyCompiler = class {
|
|
|
1843
1922
|
return { kind: "cmp", op: CMP_BY_OP[op], column, value };
|
|
1844
1923
|
}
|
|
1845
1924
|
};
|
|
1925
|
+
function assertExternalClaimComparison({ op, key, column }) {
|
|
1926
|
+
if (op !== "=") {
|
|
1927
|
+
throw new PolicyCompileError(
|
|
1928
|
+
`claim('${key}') supports = comparisons only, got '${op}'`,
|
|
1929
|
+
"unsupported-comparison"
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1932
|
+
if (column?.type !== "text") {
|
|
1933
|
+
const actual = column?.type === void 0 ? "of unknown type" : typeof column.type === "string" ? column.type : "an enum";
|
|
1934
|
+
throw new PolicyCompileError(
|
|
1935
|
+
`claim('${key}') is text and compares to a text column only \u2014 '${column?.name ?? "?"}' is ${actual}`,
|
|
1936
|
+
"claim-type-mismatch"
|
|
1937
|
+
);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
function assertNoNegatedExternalClaim(operand) {
|
|
1941
|
+
walkPolicy(operand, (node) => {
|
|
1942
|
+
if (node.kind !== "compare") return;
|
|
1943
|
+
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
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1846
1952
|
function compilePredicateV2({ expression, table, resolver, maxLinkDepth }) {
|
|
1847
1953
|
const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
|
|
1848
1954
|
const compiler = new PolicyCompiler({ resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH });
|