@vibeorm/runtime 2.0.0-alpha.8 → 2.0.0-alpha.9
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/client.d.ts +7 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +955 -19
- package/dist/index.js.map +5 -4
- package/dist/scoped.d.ts +131 -0
- package/dist/scoped.d.ts.map +1 -0
- package/package.json +5 -4
package/dist/scoped.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant scoping — `$scoped` (framework handoff: one scoped handle per request).
|
|
3
|
+
*
|
|
4
|
+
* A CALLER-CONFIGURED row-scoping layer: the base client plus a total
|
|
5
|
+
* per-model tenancy map plus a scope value produce a NEW handle whose every
|
|
6
|
+
* query is structurally confined to that tenant. Complements `@@policy`
|
|
7
|
+
* (schema-declared, fail-closed) — the two do NOT combine: `$scoped` refuses
|
|
8
|
+
* on a client whose schema declares any `@@policy`.
|
|
9
|
+
*
|
|
10
|
+
* Implemented as PURE args rewriting over the built delegates (the views.ts /
|
|
11
|
+
* policy.ts pattern — ZERO query-builder edits; every emitted statement is a
|
|
12
|
+
* shape the catalogs already pin). The rewrite relies on three verified
|
|
13
|
+
* builder properties:
|
|
14
|
+
* 1. findUnique/connect wheres compile their FULL where object — extra
|
|
15
|
+
* fields and `AND` groups become filters (query-builder findUnique,
|
|
16
|
+
* nested-writes resolveTargetKey), so injecting the tenant predicate
|
|
17
|
+
* into a unique where IS the verified lookup, inside whatever
|
|
18
|
+
* transaction the write already runs.
|
|
19
|
+
* 2. A guard field beyond the conflict target forces upsert onto the
|
|
20
|
+
* transactional find-then-write fallback (PG-2), whose probe honors the
|
|
21
|
+
* full where — so an injected upsert can never fire ON CONFLICT against
|
|
22
|
+
* another tenant's row.
|
|
23
|
+
* 3. Cursors compile from caller-supplied values only (no row lookup), so
|
|
24
|
+
* cursor pagination needs no rewrite to stay leak-free.
|
|
25
|
+
*
|
|
26
|
+
* Deny-by-default: every argument node that touches a scoped model either
|
|
27
|
+
* gets the predicate injected where semantics allow, or the call REFUSES with
|
|
28
|
+
* a typed `ScopedQueryError { model, path, reason }` before any SQL runs.
|
|
29
|
+
* Raw SQL is absent from the scoped surface; the deliberate escape is the
|
|
30
|
+
* BASE client, which this layer never mutates.
|
|
31
|
+
*/
|
|
32
|
+
import type { RelationIR } from "@vibeorm/schema";
|
|
33
|
+
import type { FieldMeta, RuntimeMeta } from "./model-meta.ts";
|
|
34
|
+
import type { ClientRow, DynamicClient } from "./client.ts";
|
|
35
|
+
/** Scope values a tenant column can carry (matches String / Int / BigInt columns). */
|
|
36
|
+
export type ScopeValue = string | number | bigint;
|
|
37
|
+
/**
|
|
38
|
+
* What happens when a scoped model appears as a CHILD inside another model's
|
|
39
|
+
* nested write: `"verify"` (default) rewrites reference verbs into
|
|
40
|
+
* tenant-verified lookups; `"refuse"` rejects every reference verb (nested
|
|
41
|
+
* `create` stays allowed under both — it is forced into the tenant).
|
|
42
|
+
*/
|
|
43
|
+
export type ScopedNestedMode = "verify" | "refuse";
|
|
44
|
+
/** One model's classification in the tenancy map. */
|
|
45
|
+
export type ScopedModelEntry = "none" | {
|
|
46
|
+
readonly column: string;
|
|
47
|
+
readonly nested?: ScopedNestedMode;
|
|
48
|
+
} | {
|
|
49
|
+
readonly through: string;
|
|
50
|
+
readonly nested?: ScopedNestedMode;
|
|
51
|
+
};
|
|
52
|
+
/** The per-model tenancy map — must classify EVERY model (totality). */
|
|
53
|
+
export type ScopedModelsMap = Readonly<Record<string, ScopedModelEntry>>;
|
|
54
|
+
/** The `$scoped` argument. */
|
|
55
|
+
export type ScopedClientConfig = {
|
|
56
|
+
readonly scope: ScopeValue | null;
|
|
57
|
+
readonly models: ScopedModelsMap;
|
|
58
|
+
};
|
|
59
|
+
type ResolvedEntry = {
|
|
60
|
+
readonly kind: "none";
|
|
61
|
+
} | {
|
|
62
|
+
readonly kind: "column";
|
|
63
|
+
readonly field: FieldMeta;
|
|
64
|
+
readonly nested: ScopedNestedMode;
|
|
65
|
+
} | {
|
|
66
|
+
readonly kind: "through";
|
|
67
|
+
readonly relation: RelationIR;
|
|
68
|
+
readonly targetModel: string;
|
|
69
|
+
readonly nested: ScopedNestedMode;
|
|
70
|
+
};
|
|
71
|
+
type ResolvedScopedModels = ReadonlyMap<string, ResolvedEntry>;
|
|
72
|
+
/**
|
|
73
|
+
* Validate the models map against the runtime meta: totality, known
|
|
74
|
+
* columns/relations, scopeable column types, acyclic through-chains ending on
|
|
75
|
+
* a { column } model. Cached by (map, meta) object identity — the framework
|
|
76
|
+
* holds one map constant and calls `$scoped` per request.
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolveScopedModels(params: {
|
|
79
|
+
meta: RuntimeMeta;
|
|
80
|
+
models: ScopedModelsMap;
|
|
81
|
+
}): ResolvedScopedModels;
|
|
82
|
+
/** A rewritten call: which base-delegate method to run, with which args. */
|
|
83
|
+
export type RewrittenScopedCall = {
|
|
84
|
+
readonly model: string;
|
|
85
|
+
readonly method: string;
|
|
86
|
+
readonly args: ClientRow | undefined;
|
|
87
|
+
};
|
|
88
|
+
/** A validated, scope-bound rewriter — the pure core a binding's tests drive. */
|
|
89
|
+
export type ScopeRewriter = {
|
|
90
|
+
readonly rewrite: (call: {
|
|
91
|
+
model: string;
|
|
92
|
+
method: string;
|
|
93
|
+
args?: ClientRow;
|
|
94
|
+
}) => RewrittenScopedCall;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Validate a models map + scope against the runtime meta and return the pure
|
|
98
|
+
* per-call rewriter (`ScopedQueryError` on refusals). This is `$scoped`'s
|
|
99
|
+
* engine without the client plumbing — conformance rewrite-vectors run here.
|
|
100
|
+
*/
|
|
101
|
+
export declare function createScopeRewriter(params: {
|
|
102
|
+
meta: RuntimeMeta;
|
|
103
|
+
models: ScopedModelsMap;
|
|
104
|
+
scope: ScopeValue | null;
|
|
105
|
+
}): ScopeRewriter;
|
|
106
|
+
/**
|
|
107
|
+
* Rewrite ONE delegate call for a scoped handle — pure: same inputs, same
|
|
108
|
+
* output; refusals throw `ScopedQueryError` before any SQL could run. This is
|
|
109
|
+
* the function the conformance rewrite-vectors pin.
|
|
110
|
+
*/
|
|
111
|
+
export declare function rewriteScopedCall(params: {
|
|
112
|
+
meta: RuntimeMeta;
|
|
113
|
+
resolved: ResolvedScopedModels;
|
|
114
|
+
scope: ScopeValue | null;
|
|
115
|
+
preds: ReadonlyMap<string, ClientRow | null>;
|
|
116
|
+
model: string;
|
|
117
|
+
method: string;
|
|
118
|
+
args: ClientRow | undefined;
|
|
119
|
+
}): RewrittenScopedCall;
|
|
120
|
+
/**
|
|
121
|
+
* Attach `$scoped` to a built client. Validation happens per call:
|
|
122
|
+
* `@@policy` schemas refuse (the two scoping layers do not combine), the
|
|
123
|
+
* models map is resolved against the schema (cached by object identity), and
|
|
124
|
+
* the scope value is type-checked against every scoped column.
|
|
125
|
+
*/
|
|
126
|
+
export declare function attachScoped(params: {
|
|
127
|
+
client: DynamicClient;
|
|
128
|
+
meta: RuntimeMeta;
|
|
129
|
+
}): void;
|
|
130
|
+
export {};
|
|
131
|
+
//# sourceMappingURL=scoped.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoped.d.ts","sourceRoot":"","sources":["../src/scoped.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeorm/runtime",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.9",
|
|
4
4
|
"description": "Driver-agnostic query engine and client runtime for VibeORM v2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"orm",
|
|
@@ -41,11 +41,12 @@
|
|
|
41
41
|
"bun": ">=1.2.0"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@vibeorm/schema": "2.0.0-alpha.
|
|
45
|
-
"@vibeorm/sql": "2.0.0-alpha.
|
|
44
|
+
"@vibeorm/schema": "2.0.0-alpha.7",
|
|
45
|
+
"@vibeorm/sql": "2.0.0-alpha.7"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@electric-sql/pglite": "^0.5.4"
|
|
48
|
+
"@electric-sql/pglite": "^0.5.4",
|
|
49
|
+
"@vibeorm/scoped-conformance": "0.1.0-alpha.0"
|
|
49
50
|
},
|
|
50
51
|
"publishConfig": {
|
|
51
52
|
"access": "public"
|