cvx-kit 0.1.14 → 0.1.16
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 +64 -45
- package/dist/querying/cursors.d.mts +5 -0
- package/dist/querying/cursors.mjs +1 -1
- package/dist/querying/cursors.mjs.map +1 -1
- package/dist/querying.d.mts +20 -6
- package/dist/querying.mjs +1 -1
- package/dist/querying.mjs.map +1 -1
- package/dist/relations.mjs +1 -1
- package/dist/relations.mjs.map +1 -1
- package/dist/zod-table.d.mts +59 -2
- package/dist/zod-table.mjs +1 -1
- package/dist/zod-table.mjs.map +1 -1
- package/package.json +10 -3
- package/AGENTS.md +0 -47
- package/CHANGELOG.md +0 -324
- package/ROADMAP.md +0 -126
- package/docs/agent-tools.md +0 -41
- package/docs/approvals.md +0 -201
- package/docs/architecture.md +0 -124
- package/docs/auth.md +0 -443
- package/docs/command-context.md +0 -150
- package/docs/commands-legacy.md +0 -513
- package/docs/commands.md +0 -173
- package/docs/conventions.md +0 -345
- package/docs/crud.md +0 -66
- package/docs/domain-contracts.md +0 -148
- package/docs/domain-operations.md +0 -615
- package/docs/effect.md +0 -403
- package/docs/llms.md +0 -324
- package/docs/maintainability.md +0 -124
- package/docs/migration.md +0 -351
- package/docs/oxlint.md +0 -254
- package/docs/querying.md +0 -293
- package/docs/tenancy.md +0 -176
- package/docs/triggers.md +0 -77
- package/docs/upgrading.md +0 -287
- package/docs/webhooks.md +0 -58
- package/docs/zod-table.md +0 -278
- package/scripts/convex-config-bridge.mjs +0 -44
- package/scripts/convex-smoke-env.mjs +0 -33
- package/scripts/extract-changelog.mjs +0 -29
- package/scripts/owned-process.mjs +0 -140
- package/scripts/owned-process.test.mjs +0 -107
- package/scripts/smoke-packed-components.mjs +0 -341
- package/scripts/smoke-packed-effect.mjs +0 -439
- package/scripts/smoke-packed-querying.mjs +0 -296
- package/scripts/test-packed-auth.mjs +0 -72
- package/scripts/test-packed-effect.mjs +0 -482
- package/scripts/test-packed-ids.mjs +0 -150
- package/scripts/test-packed-oxlint.mjs +0 -167
- package/scripts/test-packed-querying.mjs +0 -185
package/README.md
CHANGED
|
@@ -28,26 +28,47 @@ Supported versions: Convex `^1.46.0`, Zod `^4.5.4`, Bun `1.3+`, and Node.js
|
|
|
28
28
|
|
|
29
29
|
## Documentation
|
|
30
30
|
|
|
31
|
-
Full docs
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
31
|
+
Full docs live on [GitHub](https://github.com/ImRLopezAG/cvx-kit/tree/main/skills/cvx-kit/references)
|
|
32
|
+
and are bundled with the repository's [agent skill](https://github.com/ImRLopezAG/cvx-kit/tree/main/skills/cvx-kit).
|
|
33
|
+
The npm package includes this README; full docs and skills are installed separately.
|
|
34
|
+
|
|
35
|
+
If you use a coding agent, install the cvx-kit skill from your application directory:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npx skills add ImRLopezAG/cvx-kit --skill cvx-kit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The [skills CLI](https://www.skills.sh/docs) lets you choose your agent. Installation
|
|
42
|
+
is project-scoped by default; add `--global` only for a user-wide install. Public
|
|
43
|
+
GitHub installation needs no login. Preview available skills with
|
|
44
|
+
`npx skills add ImRLopezAG/cvx-kit --list`.
|
|
45
|
+
The skill reads the guides needed for each task instead of loading all docs.
|
|
46
|
+
Check your installed package version against the skill's source revision before
|
|
47
|
+
using newer APIs. For a release containing the skill, use its GitHub tag URL
|
|
48
|
+
(`https://github.com/ImRLopezAG/cvx-kit/tree/<tag>/skills/cvx-kit`) as the install source.
|
|
49
|
+
|
|
50
|
+
Maintainers edit `skills/cvx-kit/references/` directly. CI verifies that relative
|
|
51
|
+
links remain inside the installed skill with `bun run skills:check`.
|
|
52
|
+
|
|
53
|
+
Guides:
|
|
54
|
+
|
|
55
|
+
- [`architecture.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/architecture.md) — the layered application shape the kit encodes
|
|
56
|
+
- [`conventions.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/conventions.md) — the doctrine: folder structure, file anatomy, naming grammar, import rules
|
|
57
|
+
- [`zod-table.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/zod-table.md) — one zod shape per entity, masked into every boundary
|
|
58
|
+
- [`auth.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/auth.md) — auth-aware function constructors from injected policy
|
|
59
|
+
- [`commands.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/commands.md) — Effect command declarations, lifecycle, and migration
|
|
60
|
+
- [`effect.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/effect.md) — opt-in Effect v4 registries, injected services, and shared API builders
|
|
61
|
+
- [`domain-operations.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/domain-operations.md) — server domain/API examples, contracts, errors, receipts, workflow steps, and selected tools
|
|
62
|
+
- [`triggers.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/triggers.md) — trigger registry, timestamps/append-only/no-delete
|
|
63
|
+
- [`approvals.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/approvals.md) — declarative approval workflows
|
|
64
|
+
- [`tenancy.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/tenancy.md) — row-level security (roles) with opt-in multi-tenant isolation
|
|
65
|
+
- [`crud.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/crud.md) — the CRUD command factory
|
|
66
|
+
- [`webhooks.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/webhooks.md) — the sanctioned webhook boundary
|
|
67
|
+
- [`agent-tools.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/agent-tools.md) — agent tools that are audited commands
|
|
68
|
+
- [`upgrading.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/upgrading.md) — 0.0.x → 0.1.0, old way → new way
|
|
69
|
+
- [`maintainability.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/maintainability.md) — keeping a Convex app maintainable long-term
|
|
70
|
+
- [`migration.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/migration.md) — restructuring an existing raw Convex project into this architecture
|
|
71
|
+
- [`llms.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/llms.md) — optional condensed reference for coding agents
|
|
51
72
|
|
|
52
73
|
## Modules
|
|
53
74
|
|
|
@@ -85,9 +106,9 @@ LLM-tool JSON schemas primitive while preserving the `Id<...>` type.
|
|
|
85
106
|
|
|
86
107
|
### `cvx-kit/relations` and `cvx-kit/querying`
|
|
87
108
|
|
|
88
|
-
See the [querying guide](
|
|
89
|
-
[DTO parameters](
|
|
90
|
-
[consumer rule reference](
|
|
109
|
+
See the [querying guide](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/querying.md),
|
|
110
|
+
[DTO parameters](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/zod-table.md#public-dto-parameters), and
|
|
111
|
+
[consumer rule reference](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/oxlint.md#dto-and-pagination-diagnostics).
|
|
91
112
|
|
|
92
113
|
Declare relationships once in `convex/relations.ts`, then configure the engine
|
|
93
114
|
once in `convex/querying.ts`. Both factories are also available from the package
|
|
@@ -123,17 +144,15 @@ Missing and unreadable targets have the same public result.
|
|
|
123
144
|
import { createQueryEngine } from 'cvx-kit/querying'
|
|
124
145
|
import schema from './schema'
|
|
125
146
|
import { relations } from './relations'
|
|
126
|
-
import { cursor } from './functions'
|
|
127
147
|
|
|
128
|
-
export const querying = createQueryEngine({
|
|
129
|
-
schema,
|
|
130
|
-
relations,
|
|
131
|
-
cursor,
|
|
132
|
-
})
|
|
148
|
+
export const querying = createQueryEngine({ schema, relations })
|
|
133
149
|
```
|
|
134
150
|
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
One engine serves every registered table. Cursor keys load internally on first
|
|
152
|
+
pagination from server environment variables `CVX_QUERY_CURSOR_KEYS_JSON`
|
|
153
|
+
(key IDs mapped to 128-character hex secrets) and `CVX_QUERY_CURSOR_CURRENT_KEY_ID`.
|
|
154
|
+
Keep key material out of arguments, client code, and logs. Optional overrides
|
|
155
|
+
belong in a separate second argument; see [querying](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/querying.md). The default codec
|
|
137
156
|
uses authenticated AES-SIV encryption without a plaintext fallback; identical
|
|
138
157
|
payloads under the same binding reveal equality. Tokens bind filters, selected
|
|
139
158
|
output, index, order, relation ancestry, principal/tenant/security scope, and
|
|
@@ -326,12 +345,12 @@ export const executeRename = (input: z.input<typeof renameInput>, host: Host) =>
|
|
|
326
345
|
services and execute it; domain code can compose `Effect.gen` and `Effect.fn`
|
|
327
346
|
without starting another runner. Commands validate inputs and results, enforce
|
|
328
347
|
permissions and guards, and write audit entries in the mutation transaction.
|
|
329
|
-
See [commands](
|
|
330
|
-
and [authenticated builders](
|
|
348
|
+
See [commands](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/commands.md), [complete Effect wiring](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/effect.md),
|
|
349
|
+
and [authenticated builders](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/auth.md).
|
|
331
350
|
|
|
332
351
|
The old `Foundation.Command` Promise facade is **deprecated** and retained for
|
|
333
352
|
existing consumers. Its separate executor handler options are covered only in
|
|
334
|
-
the [compatibility reference](
|
|
353
|
+
the [compatibility reference](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/commands-legacy.md). The Foundation
|
|
335
354
|
component and its transaction result helpers remain available.
|
|
336
355
|
|
|
337
356
|
### `cvx-kit/components/approvals` (component)
|
|
@@ -351,7 +370,7 @@ app.use(approvals)
|
|
|
351
370
|
## Application lint rules
|
|
352
371
|
|
|
353
372
|
The TypeScript plugin `cvx-kit/oxlint` checks applications using the kit against
|
|
354
|
-
[`conventions.md`](
|
|
373
|
+
[`conventions.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/conventions.md). It targets `convex/`, including
|
|
355
374
|
public adapters, domain modules, root facades, and locally authored components.
|
|
356
375
|
Load it in your application's `.oxlintrc.json` and select rules:
|
|
357
376
|
|
|
@@ -392,11 +411,11 @@ module cycles. Run Oxlint on the whole backend, including `convex.config.ts`;
|
|
|
392
411
|
changed-file-only lint cannot guarantee a project scan. No application-owned
|
|
393
412
|
architecture test framework is required. The plugin also exports `checkArchitecture()`
|
|
394
413
|
for tools that need project diagnostics directly, named `rules`, and `RuleName`. Verified with
|
|
395
|
-
Oxlint 1.87.0 and Vite+ 1.1.0. See [rule coverage and limitations](
|
|
396
|
-
and the [migration from the initial 0.1.4 plugin](
|
|
414
|
+
Oxlint 1.87.0 and Vite+ 1.1.0. See [rule coverage and limitations](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/oxlint.md)
|
|
415
|
+
and the [migration from the initial 0.1.4 plugin](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/upgrading.md).
|
|
397
416
|
|
|
398
417
|
Repository checks and the enforced library rules are documented in
|
|
399
|
-
[`tools/oxlint/README.md`](
|
|
418
|
+
[`tools/oxlint/README.md`](https://github.com/ImRLopezAG/cvx-kit/blob/main/tools/oxlint/README.md). Run `bun run lint` for
|
|
400
419
|
anti-slop and architecture checks, `bun run test:lint` to test the custom
|
|
401
420
|
rules, and `bun run check` for the full Vite+ lint and formatting check.
|
|
402
421
|
|
|
@@ -427,7 +446,7 @@ registerApprovals(t)
|
|
|
427
446
|
## Releasing
|
|
428
447
|
|
|
429
448
|
Every release also creates a GitHub Release whose notes are the matching
|
|
430
|
-
[CHANGELOG](
|
|
449
|
+
[CHANGELOG](https://github.com/ImRLopezAG/cvx-kit/blob/main/CHANGELOG.md) section (extracted by
|
|
431
450
|
`scripts/extract-changelog.mjs` in the release workflow).
|
|
432
451
|
|
|
433
452
|
### OIDC setup
|
|
@@ -436,10 +455,10 @@ Publishing uses [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishe
|
|
|
436
455
|
(OIDC) — no tokens stored anywhere. The release workflow authenticates via
|
|
437
456
|
GitHub's OIDC and npm generates provenance attestations automatically.
|
|
438
457
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
458
|
+
Merge the version bump and changelog into `main`, then wait for successful CI
|
|
459
|
+
on that exact commit before pushing its `v<version>` tag. The tag triggers
|
|
460
|
+
`.github/workflows/release.yml`, which checks the main CI result, builds and
|
|
461
|
+
validates the package and skill documentation, and publishes to npm.
|
|
443
462
|
|
|
444
463
|
One-time setup (already-published package required first):
|
|
445
464
|
|
|
@@ -451,10 +470,10 @@ One-time setup (already-published package required first):
|
|
|
451
470
|
|
|
452
471
|
## Roadmap
|
|
453
472
|
|
|
454
|
-
See [ROADMAP.md](
|
|
473
|
+
See [ROADMAP.md](https://github.com/ImRLopezAG/cvx-kit/blob/main/ROADMAP.md) — milestones from 0.1.0 through 1.0.0.
|
|
455
474
|
|
|
456
475
|
Optional request context can be added through the existing Effect API adapter's
|
|
457
476
|
`context` callback. Bind selected registries with `commands.withContext(value)`
|
|
458
477
|
and consume `ctx.commands.exec(name, input)` through the application's existing
|
|
459
478
|
function helper names. Keep dependencies local to each feature; explicit execution
|
|
460
|
-
and input-only command handlers remain supported. See [optional context injection](
|
|
479
|
+
and input-only command handlers remain supported. See [optional context injection](https://github.com/ImRLopezAG/cvx-kit/blob/main/skills/cvx-kit/references/command-context.md).
|
|
@@ -31,6 +31,11 @@ export declare class CursorConfigurationError extends Error {
|
|
|
31
31
|
readonly _tag = "CursorConfigurationError";
|
|
32
32
|
constructor();
|
|
33
33
|
}
|
|
34
|
+
/** Private lazy server configuration. Call only at the first pagination boundary. */
|
|
35
|
+
export declare function createEnvironmentCursorCodec(): Readonly<{
|
|
36
|
+
seal(raw: string, binding: CursorBinding): string;
|
|
37
|
+
open(token: string, binding: CursorBinding): string;
|
|
38
|
+
}>;
|
|
34
39
|
/**
|
|
35
40
|
* Deterministic RFC 5297 AES-SIV: equal plaintext and binding leak equality.
|
|
36
41
|
* One navigation-boundary family permits native split/continue tokens in start/end
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{canonicalConvexBytes as e}from"../modules/contracts/idempotency.mjs";import{aessiv as t}from"@noble/ciphers/aes.js";import{bytesToHex as n,hexToBytes as r}from"@noble/ciphers/utils.js";var i=class extends Error{_tag=`CursorError`;code=`invalid-cursor`;constructor(){super(`Invalid cursor`),this.name=`CursorError`}},a=class extends Error{_tag=`CursorConfigurationError`;constructor(){super(`Invalid cursor server configuration`),this.name=`CursorConfigurationError`}};const o=Object.freeze({maxDepth:24,maxNodes:2048,maxBytes:16384,maxArrayLength:512,maxObjectFields:128}),s=8192,c=new TextEncoder,l=new TextDecoder(`utf-8`,{fatal:!0}),u=/^[A-Za-z0-9_-]{1,32}$/;function d(e){let o,d,f=/* @__PURE__ */ new Map;try{if(o=e.version,d=e.currentKeyId,typeof o!=`string`||typeof d!=`string`||!u.test(o)||!u.test(d))throw new a;let t=Object.entries(e.keys);if(!t.length)throw new a;for(let[e,n]of t){if(!u.test(e)||!(n instanceof Uint8Array)||n.byteLength!==64)throw new a;f.set(e,Uint8Array.from(n))}if(!f.has(d))throw new a}catch{throw new a}return Object.freeze({seal(e,r){try{if(typeof e!=`string`||e.length>s||c.encode(e).length>s)throw new i;let a=c.encode(JSON.stringify(e)),l=t(f.get(d),
|
|
1
|
+
import{canonicalConvexBytes as e}from"../modules/contracts/idempotency.mjs";import{aessiv as t}from"@noble/ciphers/aes.js";import{bytesToHex as n,hexToBytes as r}from"@noble/ciphers/utils.js";var i=class extends Error{_tag=`CursorError`;code=`invalid-cursor`;constructor(){super(`Invalid cursor`),this.name=`CursorError`}},a=class extends Error{_tag=`CursorConfigurationError`;constructor(){super(`Invalid cursor server configuration`),this.name=`CursorConfigurationError`}};const o=Object.freeze({maxDepth:24,maxNodes:2048,maxBytes:16384,maxArrayLength:512,maxObjectFields:128}),s=8192,c=new TextEncoder,l=new TextDecoder(`utf-8`,{fatal:!0}),u=/^[A-Za-z0-9_-]{1,32}$/;function d(){try{let e=process.env.CVX_QUERY_CURSOR_KEYS_JSON,t=process.env.CVX_QUERY_CURSOR_CURRENT_KEY_ID;if(!e||!t)throw new a;let n=JSON.parse(e);if(typeof n!=`object`||!n||Array.isArray(n))throw new a;return f({version:`1`,currentKeyId:t,keys:Object.fromEntries(Object.entries(n).map(([e,t])=>{if(typeof t!=`string`||!/^[0-9a-fA-F]{128}$/.test(t))throw new a;return[e,r(t)]}))})}catch{throw new a}}function f(e){let o,d,f=/* @__PURE__ */ new Map;try{if(o=e.version,d=e.currentKeyId,typeof o!=`string`||typeof d!=`string`||!u.test(o)||!u.test(d))throw new a;let t=Object.entries(e.keys);if(!t.length)throw new a;for(let[e,n]of t){if(!u.test(e)||!(n instanceof Uint8Array)||n.byteLength!==64)throw new a;f.set(e,Uint8Array.from(n))}if(!f.has(d))throw new a}catch{throw new a}return Object.freeze({seal(e,r){try{if(typeof e!=`string`||e.length>s||c.encode(e).length>s)throw new i;let a=c.encode(JSON.stringify(e)),l=t(f.get(d),m(o,d,r));return`pc.${o}.${d}.${n(l.encrypt(a))}`}catch{throw new i}},open(e,n){try{if(typeof e!=`string`||e.length>98410)throw new i;let a=e.split(`.`);if(a.length!==4||a[0]!==`pc`||a[1]!==o||!u.test(a[2]))throw new i;let d=a[2],p=a[3],h=f.get(d);if(!h||p.length<36||p.length>98340||p.length%2||!/^[0-9a-f]+$/.test(p))throw new i;let g=t(h,m(o,d,n)).decrypt(r(p));if(g.length>49154)throw new i;let _=l.decode(g),v=JSON.parse(_);if(typeof v!=`string`||v.length>s||c.encode(v).length>s||JSON.stringify(v)!==_)throw new i;return v}catch{throw new i}}})}function p(e){let t=/* @__PURE__ */ new Set,n=0;function r(e,a){if(++n>o.maxNodes||a>o.maxDepth)throw new i;if(typeof e!=`object`||!e||e instanceof ArrayBuffer)return;if(t.has(e))throw new i;let s=Array.isArray(e);if(s&&Object.getPrototypeOf(e)!==Array.prototype||!s&&Object.getPrototypeOf(e)!==Object.prototype&&Object.getPrototypeOf(e)!==null||Object.getOwnPropertySymbols(e).length)throw new i;let c=Object.getOwnPropertyNames(e);if(c.length>(s?o.maxArrayLength+1:o.maxObjectFields))throw new i;t.add(e);try{for(let t of c){let n=Object.getOwnPropertyDescriptor(e,t);if(!(`value`in n))throw new i;if(s&&t===`length`){if(n.value>o.maxArrayLength)throw new i;continue}if(!n.enumerable||s&&(!/^(0|[1-9][0-9]*)$/.test(t)||Number(t)>=e.length))throw new i;r(n.value,a+1)}}finally{t.delete(e)}}r(e,0)}function m(t,n,r){if(p(r),r.clientFilters===void 0||r.serverFilters===void 0)throw new i;let{table:a,order:s,principal:l,policyVersion:u,engineVersion:d,registryVersion:f}=r;for(let e of[a,l,u,d,f])if(typeof e!=`string`||!e.length)throw new i;if(s!==`asc`&&s!==`desc`)throw new i;for(let e of[r.index,r.parent,r.tenant])if(e!=null&&typeof e!=`string`)throw new i;if(!Array.isArray(r.relationPath)||r.relationPath.some(e=>typeof e!=`string`))throw new i;return c.encode(e({domain:`cvx-query-cursor`,version:t,keyId:n,table:a,index:r.index??null,order:s,clientFilters:r.clientFilters,serverFilters:r.serverFilters,relationPath:r.relationPath,parent:r.parent??null,principal:l,tenant:r.tenant??null,policyVersion:u,engineVersion:d,registryVersion:f},o))}export{a as CursorConfigurationError,i as CursorError,f as createCursorCodec,d as createEnvironmentCursorCodec};
|
|
2
2
|
//# sourceMappingURL=cursors.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursors.mjs","names":["CursorError","CursorConfigurationError","bounds","maxPlaintextBytes","encoder","decoder","identifier","createCursorCodec","version","currentKeyId","keys","config","entries","id","key","raw","payload","cipher","aessiv","aad","binding","bytesToHex","token","parts","keyId","encoded","plaintext","hexToBytes","json","checkDescriptors","ancestors","nodes","visit","depth","current","array","names","name","descriptor","value","field","table","principal","policyVersion","engineVersion","registryVersion","order","part","canonicalConvexBytes"],"sources":["../../src/querying/cursors.ts"],"sourcesContent":["/* oxlint-disable anti-slop/no-runtime-typeof, anti-slop/no-unknown-parameters -- Cursor transport and canonical values are explicit validation boundaries. */\nimport { aessiv } from '@noble/ciphers/aes.js'\nimport { bytesToHex, hexToBytes } from '@noble/ciphers/utils.js'\nimport type { Value } from 'convex/values'\nimport { canonicalConvexBytes } from '../modules/contracts/idempotency'\n\n/** Private engine contract. Bindings must come from trusted normalized engine state, never query args. */\nexport interface CursorBinding {\n\treadonly table: string\n\treadonly index?: string | null\n\treadonly order: 'asc' | 'desc'\n\treadonly clientFilters: Value\n\treadonly serverFilters: Value\n\treadonly relationPath: readonly string[]\n\treadonly parent?: string | null\n\treadonly principal: string\n\treadonly tenant?: string | null\n\treadonly policyVersion: string\n\treadonly engineVersion: string\n\treadonly registryVersion: string\n\t/** Deliberately excluded from authenticated identity. */\n\treadonly pageSize?: number\n}\nexport interface CursorCodecConfig {\n\treadonly version: string\n\treadonly currentKeyId: string\n\treadonly keys: Readonly<Record<string, Uint8Array>>\n}\nexport class CursorError extends Error {\n\treadonly _tag = 'CursorError'\n\treadonly code = 'invalid-cursor'\n\tconstructor() {\n\t\tsuper('Invalid cursor')\n\t\tthis.name = 'CursorError'\n\t}\n}\nexport class CursorConfigurationError extends Error {\n\treadonly _tag = 'CursorConfigurationError'\n\tconstructor() {\n\t\tsuper('Invalid cursor server configuration')\n\t\tthis.name = 'CursorConfigurationError'\n\t}\n}\n\nconst bounds = Object.freeze({\n\tmaxDepth: 24,\n\tmaxNodes: 2048,\n\tmaxBytes: 16384,\n\tmaxArrayLength: 512,\n\tmaxObjectFields: 128,\n})\nconst maxPlaintextBytes = 8192\n// JSON string encoding preserves even lone UTF-16 surrogates without lossy UTF-8 replacement.\nconst maxPayloadBytes = maxPlaintextBytes * 6 + 2\nconst maxCiphertextBytes = maxPayloadBytes + 16\nconst maxTokenLength = maxCiphertextBytes * 2 + 70\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder('utf-8', { fatal: true })\nconst identifier = /^[A-Za-z0-9_-]{1,32}$/\n\n/**\n * Deterministic RFC 5297 AES-SIV: equal plaintext and binding leak equality.\n * One navigation-boundary family permits native split/continue tokens in start/end\n * slots. Relation identity remains in the binding. No nonce/randomness or plaintext fallback. Opening is not authorization: the engine\n * must recheck current policy on every future request, including nested continuations.\n */\nexport function createCursorCodec(config: CursorCodecConfig) {\n\tlet version: string, currentKeyId: string\n\tconst keys = new Map<string, Uint8Array>()\n\ttry {\n\t\tversion = config.version\n\t\tcurrentKeyId = config.currentKeyId\n\t\tif (\n\t\t\ttypeof version !== 'string' ||\n\t\t\ttypeof currentKeyId !== 'string' ||\n\t\t\t!identifier.test(version) ||\n\t\t\t!identifier.test(currentKeyId)\n\t\t)\n\t\t\tthrow new CursorConfigurationError()\n\t\tconst entries = Object.entries(config.keys)\n\t\tif (!entries.length) throw new CursorConfigurationError()\n\t\tfor (const [id, key] of entries) {\n\t\t\tif (!identifier.test(id) || !(key instanceof Uint8Array) || key.byteLength !== 64)\n\t\t\t\tthrow new CursorConfigurationError()\n\t\t\tkeys.set(id, Uint8Array.from(key))\n\t\t}\n\t\tif (!keys.has(currentKeyId)) throw new CursorConfigurationError()\n\t} catch {\n\t\tthrow new CursorConfigurationError()\n\t}\n\treturn Object.freeze({\n\t\tseal(raw: string, binding: CursorBinding): string {\n\t\t\ttry {\n\t\t\t\tif (\n\t\t\t\t\ttypeof raw !== 'string' ||\n\t\t\t\t\traw.length > maxPlaintextBytes ||\n\t\t\t\t\tencoder.encode(raw).length > maxPlaintextBytes\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tconst payload = encoder.encode(JSON.stringify(raw))\n\t\t\t\tconst cipher = aessiv(keys.get(currentKeyId)!, aad(version, currentKeyId, binding))\n\t\t\t\treturn `pc.${version}.${currentKeyId}.${bytesToHex(cipher.encrypt(payload))}`\n\t\t\t} catch {\n\t\t\t\tthrow new CursorError()\n\t\t\t}\n\t\t},\n\t\topen(token: string, binding: CursorBinding): string {\n\t\t\ttry {\n\t\t\t\tif (typeof token !== 'string' || token.length > maxTokenLength) throw new CursorError()\n\t\t\t\tconst parts = token.split('.')\n\t\t\t\tif (\n\t\t\t\t\tparts.length !== 4 ||\n\t\t\t\t\tparts[0] !== 'pc' ||\n\t\t\t\t\tparts[1] !== version ||\n\t\t\t\t\t!identifier.test(parts[2]!)\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tconst keyId = parts[2]!,\n\t\t\t\t\tencoded = parts[3]!,\n\t\t\t\t\tkey = keys.get(keyId)\n\t\t\t\tif (\n\t\t\t\t\t!key ||\n\t\t\t\t\tencoded.length < 36 ||\n\t\t\t\t\tencoded.length > maxCiphertextBytes * 2 ||\n\t\t\t\t\tencoded.length % 2 ||\n\t\t\t\t\t!/^[0-9a-f]+$/.test(encoded)\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tconst cipher = aessiv(key, aad(version, keyId, binding))\n\t\t\t\tconst plaintext = cipher.decrypt(hexToBytes(encoded))\n\t\t\t\tif (plaintext.length > maxPayloadBytes) throw new CursorError()\n\t\t\t\tconst json = decoder.decode(plaintext)\n\t\t\t\tconst raw: unknown = JSON.parse(json)\n\t\t\t\tif (\n\t\t\t\t\ttypeof raw !== 'string' ||\n\t\t\t\t\traw.length > maxPlaintextBytes ||\n\t\t\t\t\tencoder.encode(raw).length > maxPlaintextBytes ||\n\t\t\t\t\tJSON.stringify(raw) !== json\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\treturn raw\n\t\t\t} catch {\n\t\t\t\tthrow new CursorError()\n\t\t\t}\n\t\t},\n\t})\n}\n\n/** Reject accessor-bearing arrays/dictionaries before the shared canonical encoder reads values. */\nfunction checkDescriptors(value: unknown): void {\n\tconst ancestors = new Set<object>()\n\tlet nodes = 0\n\tfunction visit(current: unknown, depth: number): void {\n\t\tif (++nodes > bounds.maxNodes || depth > bounds.maxDepth) throw new CursorError()\n\t\tif (current === null || typeof current !== 'object' || current instanceof ArrayBuffer) return\n\t\tif (ancestors.has(current)) throw new CursorError()\n\t\tconst array = Array.isArray(current)\n\t\tif (array && Object.getPrototypeOf(current) !== Array.prototype) throw new CursorError()\n\t\tif (\n\t\t\t!array &&\n\t\t\tObject.getPrototypeOf(current) !== Object.prototype &&\n\t\t\tObject.getPrototypeOf(current) !== null\n\t\t)\n\t\t\tthrow new CursorError()\n\t\tif (Object.getOwnPropertySymbols(current).length) throw new CursorError()\n\t\tconst names = Object.getOwnPropertyNames(current)\n\t\tif (names.length > (array ? bounds.maxArrayLength + 1 : bounds.maxObjectFields))\n\t\t\tthrow new CursorError()\n\t\tancestors.add(current)\n\t\ttry {\n\t\t\tfor (const name of names) {\n\t\t\t\tconst descriptor = Object.getOwnPropertyDescriptor(current, name)!\n\t\t\t\tif (!('value' in descriptor)) throw new CursorError()\n\t\t\t\tif (array && name === 'length') {\n\t\t\t\t\tif (descriptor.value > bounds.maxArrayLength) throw new CursorError()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\t!descriptor.enumerable ||\n\t\t\t\t\t(array && (!/^(0|[1-9][0-9]*)$/.test(name) || Number(name) >= current.length))\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tvisit(descriptor.value, depth + 1)\n\t\t\t}\n\t\t} finally {\n\t\t\tancestors.delete(current)\n\t\t}\n\t}\n\tvisit(value, 0)\n}\nfunction aad(version: string, keyId: string, binding: CursorBinding): Uint8Array {\n\tcheckDescriptors(binding)\n\tif (binding.clientFilters === undefined || binding.serverFilters === undefined)\n\t\tthrow new CursorError()\n\tconst { table, order, principal, policyVersion, engineVersion, registryVersion } = binding\n\tfor (const field of [table, principal, policyVersion, engineVersion, registryVersion])\n\t\tif (typeof field !== 'string' || !field.length) throw new CursorError()\n\tif (order !== 'asc' && order !== 'desc') throw new CursorError()\n\tfor (const field of [binding.index, binding.parent, binding.tenant])\n\t\tif (field != null && typeof field !== 'string') throw new CursorError()\n\tif (\n\t\t!Array.isArray(binding.relationPath) ||\n\t\tbinding.relationPath.some((part) => typeof part !== 'string')\n\t)\n\t\tthrow new CursorError()\n\treturn encoder.encode(\n\t\tcanonicalConvexBytes(\n\t\t\t{\n\t\t\t\tdomain: 'cvx-query-cursor',\n\t\t\t\tversion,\n\t\t\t\tkeyId,\n\t\t\t\ttable,\n\t\t\t\tindex: binding.index ?? null,\n\t\t\t\torder,\n\t\t\t\tclientFilters: binding.clientFilters,\n\t\t\t\tserverFilters: binding.serverFilters,\n\t\t\t\trelationPath: binding.relationPath,\n\t\t\t\tparent: binding.parent ?? null,\n\t\t\t\tprincipal,\n\t\t\t\ttenant: binding.tenant ?? null,\n\t\t\t\tpolicyVersion,\n\t\t\t\tengineVersion,\n\t\t\t\tregistryVersion,\n\t\t\t},\n\t\t\tbounds,\n\t\t),\n\t)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cursors.mjs","names":["CursorError","CursorConfigurationError","bounds","maxPlaintextBytes","encoder","decoder","identifier","createEnvironmentCursorCodec","encoded","currentKeyId","parsed","createCursorCodec","id","key","hexToBytes","version","keys","config","entries","raw","payload","cipher","aessiv","aad","binding","bytesToHex","token","parts","keyId","plaintext","json","checkDescriptors","ancestors","nodes","visit","depth","current","array","names","name","descriptor","value","field","table","principal","policyVersion","engineVersion","registryVersion","order","part","canonicalConvexBytes"],"sources":["../../src/querying/cursors.ts"],"sourcesContent":["/* oxlint-disable anti-slop/no-runtime-typeof, anti-slop/no-unknown-parameters -- Cursor transport and canonical values are explicit validation boundaries. */\nimport { aessiv } from '@noble/ciphers/aes.js'\nimport { bytesToHex, hexToBytes } from '@noble/ciphers/utils.js'\nimport type { Value } from 'convex/values'\nimport { canonicalConvexBytes } from '../modules/contracts/idempotency'\n\n/** Private engine contract. Bindings must come from trusted normalized engine state, never query args. */\nexport interface CursorBinding {\n\treadonly table: string\n\treadonly index?: string | null\n\treadonly order: 'asc' | 'desc'\n\treadonly clientFilters: Value\n\treadonly serverFilters: Value\n\treadonly relationPath: readonly string[]\n\treadonly parent?: string | null\n\treadonly principal: string\n\treadonly tenant?: string | null\n\treadonly policyVersion: string\n\treadonly engineVersion: string\n\treadonly registryVersion: string\n\t/** Deliberately excluded from authenticated identity. */\n\treadonly pageSize?: number\n}\nexport interface CursorCodecConfig {\n\treadonly version: string\n\treadonly currentKeyId: string\n\treadonly keys: Readonly<Record<string, Uint8Array>>\n}\nexport class CursorError extends Error {\n\treadonly _tag = 'CursorError'\n\treadonly code = 'invalid-cursor'\n\tconstructor() {\n\t\tsuper('Invalid cursor')\n\t\tthis.name = 'CursorError'\n\t}\n}\nexport class CursorConfigurationError extends Error {\n\treadonly _tag = 'CursorConfigurationError'\n\tconstructor() {\n\t\tsuper('Invalid cursor server configuration')\n\t\tthis.name = 'CursorConfigurationError'\n\t}\n}\n\nconst bounds = Object.freeze({\n\tmaxDepth: 24,\n\tmaxNodes: 2048,\n\tmaxBytes: 16384,\n\tmaxArrayLength: 512,\n\tmaxObjectFields: 128,\n})\nconst maxPlaintextBytes = 8192\n// JSON string encoding preserves even lone UTF-16 surrogates without lossy UTF-8 replacement.\nconst maxPayloadBytes = maxPlaintextBytes * 6 + 2\nconst maxCiphertextBytes = maxPayloadBytes + 16\nconst maxTokenLength = maxCiphertextBytes * 2 + 70\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder('utf-8', { fatal: true })\nconst identifier = /^[A-Za-z0-9_-]{1,32}$/\n\n/** Private lazy server configuration. Call only at the first pagination boundary. */\nexport function createEnvironmentCursorCodec() {\n\ttry {\n\t\tconst encoded = process.env.CVX_QUERY_CURSOR_KEYS_JSON\n\t\tconst currentKeyId = process.env.CVX_QUERY_CURSOR_CURRENT_KEY_ID\n\t\tif (!encoded || !currentKeyId) throw new CursorConfigurationError()\n\t\tconst parsed: unknown = JSON.parse(encoded)\n\t\tif (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))\n\t\t\tthrow new CursorConfigurationError()\n\t\tconst keys = Object.fromEntries(\n\t\t\tObject.entries(parsed).map(([id, key]) => {\n\t\t\t\tif (typeof key !== 'string' || !/^[0-9a-fA-F]{128}$/.test(key))\n\t\t\t\t\tthrow new CursorConfigurationError()\n\t\t\t\treturn [id, hexToBytes(key)]\n\t\t\t}),\n\t\t)\n\t\treturn createCursorCodec({ version: '1', currentKeyId, keys })\n\t} catch {\n\t\t// Parsing/runtime failures must never expose key material or environment values.\n\t\tthrow new CursorConfigurationError()\n\t}\n}\n\n/**\n * Deterministic RFC 5297 AES-SIV: equal plaintext and binding leak equality.\n * One navigation-boundary family permits native split/continue tokens in start/end\n * slots. Relation identity remains in the binding. No nonce/randomness or plaintext fallback. Opening is not authorization: the engine\n * must recheck current policy on every future request, including nested continuations.\n */\nexport function createCursorCodec(config: CursorCodecConfig) {\n\tlet version: string, currentKeyId: string\n\tconst keys = new Map<string, Uint8Array>()\n\ttry {\n\t\tversion = config.version\n\t\tcurrentKeyId = config.currentKeyId\n\t\tif (\n\t\t\ttypeof version !== 'string' ||\n\t\t\ttypeof currentKeyId !== 'string' ||\n\t\t\t!identifier.test(version) ||\n\t\t\t!identifier.test(currentKeyId)\n\t\t)\n\t\t\tthrow new CursorConfigurationError()\n\t\tconst entries = Object.entries(config.keys)\n\t\tif (!entries.length) throw new CursorConfigurationError()\n\t\tfor (const [id, key] of entries) {\n\t\t\tif (!identifier.test(id) || !(key instanceof Uint8Array) || key.byteLength !== 64)\n\t\t\t\tthrow new CursorConfigurationError()\n\t\t\tkeys.set(id, Uint8Array.from(key))\n\t\t}\n\t\tif (!keys.has(currentKeyId)) throw new CursorConfigurationError()\n\t} catch {\n\t\tthrow new CursorConfigurationError()\n\t}\n\treturn Object.freeze({\n\t\tseal(raw: string, binding: CursorBinding): string {\n\t\t\ttry {\n\t\t\t\tif (\n\t\t\t\t\ttypeof raw !== 'string' ||\n\t\t\t\t\traw.length > maxPlaintextBytes ||\n\t\t\t\t\tencoder.encode(raw).length > maxPlaintextBytes\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tconst payload = encoder.encode(JSON.stringify(raw))\n\t\t\t\tconst cipher = aessiv(keys.get(currentKeyId)!, aad(version, currentKeyId, binding))\n\t\t\t\treturn `pc.${version}.${currentKeyId}.${bytesToHex(cipher.encrypt(payload))}`\n\t\t\t} catch {\n\t\t\t\tthrow new CursorError()\n\t\t\t}\n\t\t},\n\t\topen(token: string, binding: CursorBinding): string {\n\t\t\ttry {\n\t\t\t\tif (typeof token !== 'string' || token.length > maxTokenLength) throw new CursorError()\n\t\t\t\tconst parts = token.split('.')\n\t\t\t\tif (\n\t\t\t\t\tparts.length !== 4 ||\n\t\t\t\t\tparts[0] !== 'pc' ||\n\t\t\t\t\tparts[1] !== version ||\n\t\t\t\t\t!identifier.test(parts[2]!)\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tconst keyId = parts[2]!,\n\t\t\t\t\tencoded = parts[3]!,\n\t\t\t\t\tkey = keys.get(keyId)\n\t\t\t\tif (\n\t\t\t\t\t!key ||\n\t\t\t\t\tencoded.length < 36 ||\n\t\t\t\t\tencoded.length > maxCiphertextBytes * 2 ||\n\t\t\t\t\tencoded.length % 2 ||\n\t\t\t\t\t!/^[0-9a-f]+$/.test(encoded)\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tconst cipher = aessiv(key, aad(version, keyId, binding))\n\t\t\t\tconst plaintext = cipher.decrypt(hexToBytes(encoded))\n\t\t\t\tif (plaintext.length > maxPayloadBytes) throw new CursorError()\n\t\t\t\tconst json = decoder.decode(plaintext)\n\t\t\t\tconst raw: unknown = JSON.parse(json)\n\t\t\t\tif (\n\t\t\t\t\ttypeof raw !== 'string' ||\n\t\t\t\t\traw.length > maxPlaintextBytes ||\n\t\t\t\t\tencoder.encode(raw).length > maxPlaintextBytes ||\n\t\t\t\t\tJSON.stringify(raw) !== json\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\treturn raw\n\t\t\t} catch {\n\t\t\t\tthrow new CursorError()\n\t\t\t}\n\t\t},\n\t})\n}\n\n/** Reject accessor-bearing arrays/dictionaries before the shared canonical encoder reads values. */\nfunction checkDescriptors(value: unknown): void {\n\tconst ancestors = new Set<object>()\n\tlet nodes = 0\n\tfunction visit(current: unknown, depth: number): void {\n\t\tif (++nodes > bounds.maxNodes || depth > bounds.maxDepth) throw new CursorError()\n\t\tif (current === null || typeof current !== 'object' || current instanceof ArrayBuffer) return\n\t\tif (ancestors.has(current)) throw new CursorError()\n\t\tconst array = Array.isArray(current)\n\t\tif (array && Object.getPrototypeOf(current) !== Array.prototype) throw new CursorError()\n\t\tif (\n\t\t\t!array &&\n\t\t\tObject.getPrototypeOf(current) !== Object.prototype &&\n\t\t\tObject.getPrototypeOf(current) !== null\n\t\t)\n\t\t\tthrow new CursorError()\n\t\tif (Object.getOwnPropertySymbols(current).length) throw new CursorError()\n\t\tconst names = Object.getOwnPropertyNames(current)\n\t\tif (names.length > (array ? bounds.maxArrayLength + 1 : bounds.maxObjectFields))\n\t\t\tthrow new CursorError()\n\t\tancestors.add(current)\n\t\ttry {\n\t\t\tfor (const name of names) {\n\t\t\t\tconst descriptor = Object.getOwnPropertyDescriptor(current, name)!\n\t\t\t\tif (!('value' in descriptor)) throw new CursorError()\n\t\t\t\tif (array && name === 'length') {\n\t\t\t\t\tif (descriptor.value > bounds.maxArrayLength) throw new CursorError()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\t!descriptor.enumerable ||\n\t\t\t\t\t(array && (!/^(0|[1-9][0-9]*)$/.test(name) || Number(name) >= current.length))\n\t\t\t\t)\n\t\t\t\t\tthrow new CursorError()\n\t\t\t\tvisit(descriptor.value, depth + 1)\n\t\t\t}\n\t\t} finally {\n\t\t\tancestors.delete(current)\n\t\t}\n\t}\n\tvisit(value, 0)\n}\nfunction aad(version: string, keyId: string, binding: CursorBinding): Uint8Array {\n\tcheckDescriptors(binding)\n\tif (binding.clientFilters === undefined || binding.serverFilters === undefined)\n\t\tthrow new CursorError()\n\tconst { table, order, principal, policyVersion, engineVersion, registryVersion } = binding\n\tfor (const field of [table, principal, policyVersion, engineVersion, registryVersion])\n\t\tif (typeof field !== 'string' || !field.length) throw new CursorError()\n\tif (order !== 'asc' && order !== 'desc') throw new CursorError()\n\tfor (const field of [binding.index, binding.parent, binding.tenant])\n\t\tif (field != null && typeof field !== 'string') throw new CursorError()\n\tif (\n\t\t!Array.isArray(binding.relationPath) ||\n\t\tbinding.relationPath.some((part) => typeof part !== 'string')\n\t)\n\t\tthrow new CursorError()\n\treturn encoder.encode(\n\t\tcanonicalConvexBytes(\n\t\t\t{\n\t\t\t\tdomain: 'cvx-query-cursor',\n\t\t\t\tversion,\n\t\t\t\tkeyId,\n\t\t\t\ttable,\n\t\t\t\tindex: binding.index ?? null,\n\t\t\t\torder,\n\t\t\t\tclientFilters: binding.clientFilters,\n\t\t\t\tserverFilters: binding.serverFilters,\n\t\t\t\trelationPath: binding.relationPath,\n\t\t\t\tparent: binding.parent ?? null,\n\t\t\t\tprincipal,\n\t\t\t\ttenant: binding.tenant ?? null,\n\t\t\t\tpolicyVersion,\n\t\t\t\tengineVersion,\n\t\t\t\tregistryVersion,\n\t\t\t},\n\t\t\tbounds,\n\t\t),\n\t)\n}\n"],"mappings":"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"}
|
package/dist/querying.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ManagedBudgets, ManagedQueryContext, ManagedSecurityConfig } from "./querying/security.mjs";
|
|
2
2
|
import { IndexCondition, NativePagination } from "./querying/filters.mjs";
|
|
3
|
-
import { RelationDeclarations, RelationFilterSelection, RelationFilters, RelationRegistry, RelationRow, RelationSelection,
|
|
3
|
+
import { RelationDeclarations, RelationFilterSelection, RelationFilters, RelationRegistry, RelationRow, RelationSelection, RelationTables } from "./relations.mjs";
|
|
4
4
|
import { CursorCodecConfig } from "./querying/cursors.mjs";
|
|
5
5
|
import { DataModelFromSchemaDefinition, GenericSchema, PaginationResult, SchemaDefinition } from "convex/server";
|
|
6
6
|
import { GenericId } from "convex/values";
|
|
@@ -21,19 +21,33 @@ export interface TableQuery<Tables extends RelationTables, D extends RelationDec
|
|
|
21
21
|
relation<const P extends Paths<D, Extract<T, string>>>(parent: GenericId<Tables[T]['tableName']>, path: P): Omit<TableQuery<Tables, D, Extract<PathTarget<D, Extract<T, string>, P>, keyof Tables>, {}, RemainingDepth<P, L>>, 'relation'>;
|
|
22
22
|
paginate(args: EngineArgs<Tables, D, T>): Promise<PaginationResult<RelationRow<Tables, D, T, S>>>;
|
|
23
23
|
}
|
|
24
|
-
export interface
|
|
24
|
+
export interface QueryEngineDeclaration<Schema extends SchemaDefinition<GenericSchema, boolean>, Tables extends RelationTables, D extends RelationDeclarations> {
|
|
25
25
|
schema: Schema;
|
|
26
26
|
relations: RelationRegistry<Tables, D>;
|
|
27
|
-
|
|
27
|
+
}
|
|
28
|
+
/** Advanced server configuration, separate from the normal schema/relations declaration. */
|
|
29
|
+
export interface QueryEngineOptions<Schema extends SchemaDefinition<GenericSchema, boolean>> {
|
|
30
|
+
/** Explicit codecs validate at creation. Otherwise environment keys load at first paginate. */
|
|
31
|
+
cursor?: CursorCodecConfig;
|
|
28
32
|
budgets?: Partial<ManagedBudgets>;
|
|
29
33
|
maxPageSize?: number;
|
|
30
34
|
policyVersion?: string;
|
|
31
35
|
engineVersion?: string;
|
|
32
36
|
trustedAdapter?: ManagedSecurityConfig<DataModelFromSchemaDefinition<Schema>>['trustedAdapter'];
|
|
33
37
|
}
|
|
34
|
-
/**
|
|
35
|
-
export
|
|
36
|
-
|
|
38
|
+
/** @deprecated Use QueryEngineDeclaration and optional QueryEngineOptions separately. */
|
|
39
|
+
export interface QueryEngineConfig<Schema extends SchemaDefinition<GenericSchema, boolean>, Tables extends RelationTables, D extends RelationDeclarations> extends QueryEngineDeclaration<Schema, Tables, D>, QueryEngineOptions<Schema> {
|
|
40
|
+
cursor: CursorCodecConfig;
|
|
41
|
+
}
|
|
42
|
+
type QueryEngine<Schema extends SchemaDefinition<GenericSchema, boolean>, Tables extends RelationTables, D extends RelationDeclarations> = {
|
|
43
|
+
(ctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>): { [T in keyof Tables]: TableQuery<Tables, D, T>; };
|
|
44
|
+
tables: RelationRegistry<Tables, D>['tables'];
|
|
37
45
|
};
|
|
46
|
+
/** Central facade. Endpoint chains carry declarative constraints and explicit relationships. */
|
|
47
|
+
export declare function createQueryEngine<Schema extends SchemaDefinition<GenericSchema, boolean>, const Tables extends RelationTables, const D extends RelationDeclarations>(config: QueryEngineDeclaration<Schema, Tables, D>, options?: QueryEngineOptions<Schema>): QueryEngine<Schema, Tables, D>;
|
|
48
|
+
/** @deprecated Pass advanced fields in the optional second argument. */
|
|
49
|
+
export declare function createQueryEngine<Schema extends SchemaDefinition<GenericSchema, boolean>, const Tables extends RelationTables, const D extends RelationDeclarations>(config: QueryEngineDeclaration<Schema, Tables, D> & QueryEngineOptions<Schema> & {
|
|
50
|
+
cursor: CursorCodecConfig;
|
|
51
|
+
}): QueryEngine<Schema, Tables, D>;
|
|
38
52
|
//#endregion
|
|
39
53
|
//# sourceMappingURL=querying.d.mts.map
|
package/dist/querying.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createManagedSecurity as e}from"./querying/security.mjs";import{createPlanner as t}from"./querying/planner.mjs";import{createCursorCodec as n}from"./querying/cursors.mjs";import{assertRelationshipDepth as
|
|
1
|
+
import{createManagedSecurity as e}from"./querying/security.mjs";import{createPlanner as t}from"./querying/planner.mjs";import{createCursorCodec as n,createEnvironmentCursorCodec as r}from"./querying/cursors.mjs";import{assertRelationshipDepth as i,relationshipExecutor as a}from"./querying/relationships.mjs";import{executeQuery as o}from"./querying/execution.mjs";function s(s,c){let{schema:l,relations:u}=s,d={...s,...c},f=t(l,u),p=d.cursor===void 0?void 0:n(d.cursor),m=d.maxPageSize??100;if(!Number.isSafeInteger(m)||m<=0||m>100)throw Error(`Invalid central page size`);let h=d.engineVersion??`scalar-1`,g=d.policyVersion??`1`,_,v=e({tables:Object.keys(u.tables),policyVersion:g,budgets:d.budgets,trustedAdapter:d.trustedAdapter});function y(e){f.assertUnchanged();let t=v(e);function n(e,s=[],c=[],d={},v){return Object.freeze({when:(t,r)=>n(e,[...s,{value:t,index:r}],c,d,v),where:t=>n(e,s,[...c,t],d,v),with:t=>n(e,s,c,t,v),relation:(t,r)=>{let i=r.split(`.`);if(!i.length||i.length>4)throw Error(`INVALID_RELATION`);let a=e;for(let e of i){let t=f.metadata.declarations[a]?.[e];if(!t)throw Error(`INVALID_RELATION`);a=t.target}return n(a,s,c,{},{table:e,parent:t,path:i})},async paginate(n){let y=p??=r();f.assertUnchanged();let b=f.page(e,d);i(d,v?.path.length??0);let x=f.plan(e,n,v?[]:s,c);if(x.args.numItems>m)throw Error(`Page size exceeds central ceiling`);x.args.maximumRowsRead=Math.min(x.args.maximumRowsRead??t.budgets.rawCandidates,t.budgets.rawCandidates),x.args.maximumBytesRead=Math.min(x.args.maximumBytesRead??t.budgets.documentBytes,t.budgets.documentBytes),_??=Array.from(new Uint8Array(await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(u.identity))),e=>e.toString(16).padStart(2,`0`)).join(``),f.assertUnchanged();let S={table:v?.table??e,index:x.index?.name??`by_creation_time`,order:x.args.order??`asc`,clientFilters:x.clientIdentity,serverFilters:{predicates:x.serverIdentity,selection:d,scope:{...t.scope}},relationPath:[],principal:JSON.stringify(t.scope),tenant:t.scope.tenant,policyVersion:g,engineVersion:h,registryVersion:_},C=a(l,t,f,y,S);if(v)return C.continuation(v.table,v.parent,v.path,d,x.args,s,c);let w=await o(l,t,e,x,f,y,S,async(t,n)=>!n||!await C.matches(e,t,x.residual)?null:C.output(e,t,d,x.args));return f.assertUnchanged(),b.parse(w)}})}return Object.freeze(Object.fromEntries(Object.keys(u.tables).map(e=>[e,n(e)])))}return Object.assign(y,{tables:u.tables})}export{s as createQueryEngine};
|
|
2
2
|
//# sourceMappingURL=querying.mjs.map
|
package/dist/querying.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"querying.mjs","names":["executorRequest","createQueryEngine","config","planner","createPlanner","schema","relations","codec","createCursorCodec","maxPageSize","engineVersion","policyVersion","registryDigest","security","createManagedSecurity","querying","request","ctx","tableQuery","table","conditions","constraints","selection","route","spec","selected","path","declaredPath","target","name","edge","pageSchema","assertRelationshipDepth","plan","input","byte","binding","executor","relationshipExecutor","result","executeQuery","permitted","row"],"sources":["../src/querying.ts"],"sourcesContent":["import type {\n\tGenericSchema,\n\tSchemaDefinition,\n\tDataModelFromSchemaDefinition,\n\tPaginationResult,\n} from 'convex/server'\nimport type { GenericId } from 'convex/values'\nimport type {\n\tRelationTables,\n\tRelationDeclarations,\n\tRelationRegistry,\n\tRelationRow,\n\tRelationSelection,\n\tRelationFilters,\n\tRelationFilterSelection,\n} from './relations'\nimport { createPlanner, type QueryInput, type StoredConstraint } from './querying/planner'\nimport { executeQuery, type ExecutionRequest } from './querying/execution'\nimport {\n\trelationshipExecutor,\n\tassertRelationshipDepth,\n\ttype OutputSelection,\n\ttype RelationDocument,\n} from './querying/relationships'\nimport { createCursorCodec, type CursorCodecConfig, type CursorBinding } from './querying/cursors'\nimport {\n\tcreateManagedSecurity,\n\ttype ManagedSecurityConfig,\n\ttype ManagedQueryContext,\n\ttype ManagedBudgets,\n} from './querying/security'\nimport type { IndexCondition, NativePagination } from './querying/filters'\n\ntype FullMask<S> = {\n\t[K in keyof S]-?: NonNullable<S[K]> extends true ? true : FullMask<NonNullable<S[K]>>\n}\ntype Paths<\n\tD extends RelationDeclarations,\n\tT extends string,\n\tL extends readonly 1[] = [1, 1, 1, 1],\n> = L extends readonly [1, ...infer Rest extends 1[]]\n\t? {\n\t\t\t[K in keyof D[T] & string]:\n\t\t\t\t| (D[T][K]['cardinality'] extends 'many' ? K : never)\n\t\t\t\t| `${K}.${Paths<D, D[T][K]['target'], Rest>}`\n\t\t}[keyof D[T] & string]\n\t: never\ntype PathTarget<\n\tD extends RelationDeclarations,\n\tT extends string,\n\tP extends string,\n> = P extends `${infer Head}.${infer Rest}`\n\t? PathTarget<D, D[T][Head]['target'], Rest>\n\t: D[T][P]['target']\ntype RemainingDepth<P extends string, L extends readonly 1[]> = L extends readonly [\n\t1,\n\t...infer Rest extends 1[],\n]\n\t? P extends `${string}.${infer Tail}`\n\t\t? RemainingDepth<Tail, Rest>\n\t\t: Rest\n\t: []\ntype EngineArgs<\n\tTables extends RelationTables,\n\tD extends RelationDeclarations,\n\tT extends keyof Tables,\n> = NativePagination & {\n\tfilters?: RelationFilters<Tables, D, T, FullMask<RelationFilterSelection<Tables, D, T>>>\n\torder?: 'asc' | 'desc'\n}\n/** Typed declarative chain returned by the engine; named for consumer declaration emission. */\nexport interface TableQuery<\n\tTables extends RelationTables,\n\tD extends RelationDeclarations,\n\tT extends keyof Tables,\n\tS = {},\n\tL extends readonly 1[] = [1, 1, 1, 1],\n> {\n\twhen(value: IndexCondition['value'], index: string): TableQuery<Tables, D, T, S, L>\n\twhere(spec: NonNullable<EngineArgs<Tables, D, T>['filters']>): TableQuery<Tables, D, T, S, L>\n\twith<const Selection extends RelationSelection<Tables, D, T, L>>(\n\t\tselection: Selection & {\n\t\t\t[K in keyof Selection]: K extends keyof RelationSelection<Tables, D, T, L>\n\t\t\t\t? Selection[K]\n\t\t\t\t: never\n\t\t},\n\t): TableQuery<Tables, D, T, Selection, L>\n\trelation<const P extends Paths<D, Extract<T, string>>>(\n\t\tparent: GenericId<Tables[T]['tableName']>,\n\t\tpath: P,\n\t): Omit<\n\t\tTableQuery<\n\t\t\tTables,\n\t\t\tD,\n\t\t\tExtract<PathTarget<D, Extract<T, string>, P>, keyof Tables>,\n\t\t\t{},\n\t\t\tRemainingDepth<P, L>\n\t\t>,\n\t\t'relation'\n\t>\n\tpaginate(args: EngineArgs<Tables, D, T>): Promise<PaginationResult<RelationRow<Tables, D, T, S>>>\n}\nexport interface QueryEngineConfig<\n\tSchema extends SchemaDefinition<GenericSchema, boolean>,\n\tTables extends RelationTables,\n\tD extends RelationDeclarations,\n> {\n\tschema: Schema\n\trelations: RelationRegistry<Tables, D>\n\tcursor: CursorCodecConfig\n\tbudgets?: Partial<ManagedBudgets>\n\tmaxPageSize?: number\n\tpolicyVersion?: string\n\tengineVersion?: string\n\ttrustedAdapter?: ManagedSecurityConfig<DataModelFromSchemaDefinition<Schema>>['trustedAdapter']\n}\n\n/** Central facade. Endpoint chains carry declarative constraints and explicit relationships. */\nexport function createQueryEngine<\n\tSchema extends SchemaDefinition<GenericSchema, boolean>,\n\tconst Tables extends RelationTables,\n\tconst D extends RelationDeclarations,\n>(config: QueryEngineConfig<Schema, Tables, D>) {\n\tconst { schema, relations } = config\n\tconst planner = createPlanner(schema, relations)\n\tconst codec = createCursorCodec(config.cursor)\n\tconst maxPageSize = config.maxPageSize ?? 100\n\tif (!Number.isSafeInteger(maxPageSize) || maxPageSize <= 0 || maxPageSize > 100)\n\t\tthrow new Error('Invalid central page size')\n\tconst engineVersion = config.engineVersion ?? 'scalar-1'\n\tconst policyVersion = config.policyVersion ?? '1'\n\t// Bind the full immutable identity through a collision-resistant digest so\n\t// real application registries fit the codec's bounded AAD. Live guards still\n\t// inspect current schemas; this caches only the immutable version identity.\n\tlet registryDigest: string | undefined\n\tconst security = createManagedSecurity<DataModelFromSchemaDefinition<Schema>>({\n\t\ttables: Object.keys(relations.tables),\n\t\tpolicyVersion,\n\t\tbudgets: config.budgets,\n\t\ttrustedAdapter: config.trustedAdapter,\n\t})\n\tfunction querying(ctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>) {\n\t\tplanner.assertUnchanged()\n\t\tconst request = security(ctx)\n\t\tfunction tableQuery(\n\t\t\ttable: string,\n\t\t\tconditions: readonly IndexCondition[] = [],\n\t\t\tconstraints: readonly StoredConstraint[] = [],\n\t\t\tselection: OutputSelection = {},\n\t\t\troute?: { table: string; parent: string; path: string[] },\n\t\t) {\n\t\t\treturn Object.freeze({\n\t\t\t\twhen: (value: IndexCondition['value'], index: string) =>\n\t\t\t\t\ttableQuery(table, [...conditions, { value, index }], constraints, selection, route),\n\t\t\t\twhere: (spec: StoredConstraint) =>\n\t\t\t\t\ttableQuery(table, conditions, [...constraints, spec], selection, route),\n\t\t\t\twith: (selected: OutputSelection) =>\n\t\t\t\t\ttableQuery(table, conditions, constraints, selected, route),\n\t\t\t\trelation: (parent: string, declaredPath: string) => {\n\t\t\t\t\tconst path = declaredPath.split('.')\n\t\t\t\t\tif (!path.length || path.length > 4) throw new Error('INVALID_RELATION')\n\t\t\t\t\tlet target = table\n\t\t\t\t\tfor (const name of path) {\n\t\t\t\t\t\tconst edge = planner.metadata.declarations[target]?.[name]\n\t\t\t\t\t\tif (!edge) throw new Error('INVALID_RELATION')\n\t\t\t\t\t\ttarget = edge.target\n\t\t\t\t\t}\n\t\t\t\t\treturn tableQuery(target, conditions, constraints, {}, { table, parent, path })\n\t\t\t\t},\n\t\t\t\tasync paginate(input: QueryInput) {\n\t\t\t\t\tplanner.assertUnchanged()\n\t\t\t\t\tconst pageSchema = planner.page(table, selection)\n\t\t\t\t\tassertRelationshipDepth(selection, route?.path.length ?? 0)\n\t\t\t\t\tconst plan = planner.plan(table, input, route ? [] : conditions, constraints)\n\t\t\t\t\tif (plan.args.numItems > maxPageSize) throw new Error('Page size exceeds central ceiling')\n\t\t\t\t\t// Native root limits stay native; lowering those limits cannot raise\n\t\t\t\t\t// the independent shared request meter.\n\t\t\t\t\tplan.args.maximumRowsRead = Math.min(\n\t\t\t\t\t\tplan.args.maximumRowsRead ?? request.budgets.rawCandidates,\n\t\t\t\t\t\trequest.budgets.rawCandidates,\n\t\t\t\t\t)\n\t\t\t\t\tplan.args.maximumBytesRead = Math.min(\n\t\t\t\t\t\tplan.args.maximumBytesRead ?? request.budgets.documentBytes,\n\t\t\t\t\t\trequest.budgets.documentBytes,\n\t\t\t\t\t)\n\t\t\t\t\tregistryDigest ??= Array.from(\n\t\t\t\t\t\tnew Uint8Array(\n\t\t\t\t\t\t\tawait crypto.subtle.digest('SHA-256', new TextEncoder().encode(relations.identity)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(byte) => byte.toString(16).padStart(2, '0'),\n\t\t\t\t\t).join('')\n\t\t\t\t\t// The digest await is a boundary too: mutation must be detected\n\t\t\t\t\t// before authenticated cursor validation or native query construction.\n\t\t\t\t\tplanner.assertUnchanged()\n\t\t\t\t\t// SAFETY: selection has passed the registry page validator before binding.\n\t\t\t\t\tconst binding: CursorBinding = {\n\t\t\t\t\t\ttable: route?.table ?? table,\n\t\t\t\t\t\tindex: plan.index?.name ?? 'by_creation_time',\n\t\t\t\t\t\torder: plan.args.order ?? 'asc',\n\t\t\t\t\t\tclientFilters: plan.clientIdentity,\n\t\t\t\t\t\t// Include output selection and the complete immutable scope in trusted identity.\n\t\t\t\t\t\tserverFilters: {\n\t\t\t\t\t\t\tpredicates: plan.serverIdentity,\n\t\t\t\t\t\t\tselection: selection as import('convex/values').Value,\n\t\t\t\t\t\t\tscope: { ...request.scope },\n\t\t\t\t\t\t},\n\t\t\t\t\t\trelationPath: [],\n\t\t\t\t\t\tprincipal: JSON.stringify(request.scope),\n\t\t\t\t\t\ttenant: request.scope.tenant,\n\t\t\t\t\t\tpolicyVersion,\n\t\t\t\t\t\tengineVersion,\n\t\t\t\t\t\tregistryVersion: registryDigest,\n\t\t\t\t\t}\n\t\t\t\t\t// The executor accepts registry-selected native tables, rather than a static DM key.\n\t\t\t\t\tconst executorRequest: unknown = request\n\t\t\t\t\t// SAFETY: only native DM types are erased at this private executor boundary.\n\t\t\t\t\tconst executor = relationshipExecutor(\n\t\t\t\t\t\tschema,\n\t\t\t\t\t\texecutorRequest as ExecutionRequest,\n\t\t\t\t\t\tplanner,\n\t\t\t\t\t\tcodec,\n\t\t\t\t\t\tbinding,\n\t\t\t\t\t)\n\t\t\t\t\tif (route)\n\t\t\t\t\t\treturn executor.continuation(\n\t\t\t\t\t\t\troute.table,\n\t\t\t\t\t\t\troute.parent,\n\t\t\t\t\t\t\troute.path,\n\t\t\t\t\t\t\tselection,\n\t\t\t\t\t\t\tplan.args,\n\t\t\t\t\t\t\tconditions,\n\t\t\t\t\t\t\tconstraints,\n\t\t\t\t\t\t)\n\t\t\t\t\tconst result = await executeQuery(\n\t\t\t\t\t\tschema,\n\t\t\t\t\t\trequest,\n\t\t\t\t\t\ttable,\n\t\t\t\t\t\tplan,\n\t\t\t\t\t\tplanner,\n\t\t\t\t\t\tcodec,\n\t\t\t\t\t\tbinding,\n\t\t\t\t\t\tasync (row, permitted) => {\n\t\t\t\t\t\t\t// SAFETY: metered native stream rows contain the validated system fields.\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!permitted ||\n\t\t\t\t\t\t\t\t!(await executor.matches(table, row as RelationDocument, plan.residual))\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t\t// SAFETY: the same native row is authorized and matches the declared predicates.\n\t\t\t\t\t\t\treturn executor.output(table, row as RelationDocument, selection, plan.args)\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t\tplanner.assertUnchanged()\n\t\t\t\t\treturn pageSchema.parse(result)\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\t// SAFETY: each key comes from the exact validated registry; the planner\n\t\t// validates all input and projection while mapped types retain table brands.\n\t\treturn Object.freeze(\n\t\t\tObject.fromEntries(Object.keys(relations.tables).map((table) => [table, tableQuery(table)])),\n\t\t) as { [T in keyof Tables]: TableQuery<Tables, D, T> }\n\t}\n\treturn Object.assign(querying, { tables: relations.tables })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"querying.mjs","names":["executorRequest","createQueryEngine","config","settings","options","planner","createPlanner","schema","relations","cursorCodec","createCursorCodec","maxPageSize","engineVersion","policyVersion","registryDigest","security","createManagedSecurity","querying","request","ctx","tableQuery","table","conditions","constraints","selection","route","spec","selected","path","declaredPath","target","name","edge","codec","createEnvironmentCursorCodec","pageSchema","assertRelationshipDepth","plan","input","byte","binding","executor","relationshipExecutor","result","executeQuery","permitted","row"],"sources":["../src/querying.ts"],"sourcesContent":["import type {\n\tGenericSchema,\n\tSchemaDefinition,\n\tDataModelFromSchemaDefinition,\n\tPaginationResult,\n} from 'convex/server'\nimport type { GenericId } from 'convex/values'\nimport type {\n\tRelationTables,\n\tRelationDeclarations,\n\tRelationRegistry,\n\tRelationRow,\n\tRelationSelection,\n\tRelationFilters,\n\tRelationFilterSelection,\n} from './relations'\nimport { createPlanner, type QueryInput, type StoredConstraint } from './querying/planner'\nimport { executeQuery, type ExecutionRequest } from './querying/execution'\nimport {\n\trelationshipExecutor,\n\tassertRelationshipDepth,\n\ttype OutputSelection,\n\ttype RelationDocument,\n} from './querying/relationships'\nimport {\n\tcreateCursorCodec,\n\tcreateEnvironmentCursorCodec,\n\ttype CursorCodecConfig,\n\ttype CursorBinding,\n} from './querying/cursors'\nimport {\n\tcreateManagedSecurity,\n\ttype ManagedSecurityConfig,\n\ttype ManagedQueryContext,\n\ttype ManagedBudgets,\n} from './querying/security'\nimport type { IndexCondition, NativePagination } from './querying/filters'\n\ntype FullMask<S> = {\n\t[K in keyof S]-?: NonNullable<S[K]> extends true ? true : FullMask<NonNullable<S[K]>>\n}\ntype Paths<\n\tD extends RelationDeclarations,\n\tT extends string,\n\tL extends readonly 1[] = [1, 1, 1, 1],\n> = L extends readonly [1, ...infer Rest extends 1[]]\n\t? {\n\t\t\t[K in keyof D[T] & string]:\n\t\t\t\t| (D[T][K]['cardinality'] extends 'many' ? K : never)\n\t\t\t\t| `${K}.${Paths<D, D[T][K]['target'], Rest>}`\n\t\t}[keyof D[T] & string]\n\t: never\ntype PathTarget<\n\tD extends RelationDeclarations,\n\tT extends string,\n\tP extends string,\n> = P extends `${infer Head}.${infer Rest}`\n\t? PathTarget<D, D[T][Head]['target'], Rest>\n\t: D[T][P]['target']\ntype RemainingDepth<P extends string, L extends readonly 1[]> = L extends readonly [\n\t1,\n\t...infer Rest extends 1[],\n]\n\t? P extends `${string}.${infer Tail}`\n\t\t? RemainingDepth<Tail, Rest>\n\t\t: Rest\n\t: []\ntype EngineArgs<\n\tTables extends RelationTables,\n\tD extends RelationDeclarations,\n\tT extends keyof Tables,\n> = NativePagination & {\n\tfilters?: RelationFilters<Tables, D, T, FullMask<RelationFilterSelection<Tables, D, T>>>\n\torder?: 'asc' | 'desc'\n}\n/** Typed declarative chain returned by the engine; named for consumer declaration emission. */\nexport interface TableQuery<\n\tTables extends RelationTables,\n\tD extends RelationDeclarations,\n\tT extends keyof Tables,\n\tS = {},\n\tL extends readonly 1[] = [1, 1, 1, 1],\n> {\n\twhen(value: IndexCondition['value'], index: string): TableQuery<Tables, D, T, S, L>\n\twhere(spec: NonNullable<EngineArgs<Tables, D, T>['filters']>): TableQuery<Tables, D, T, S, L>\n\twith<const Selection extends RelationSelection<Tables, D, T, L>>(\n\t\tselection: Selection & {\n\t\t\t[K in keyof Selection]: K extends keyof RelationSelection<Tables, D, T, L>\n\t\t\t\t? Selection[K]\n\t\t\t\t: never\n\t\t},\n\t): TableQuery<Tables, D, T, Selection, L>\n\trelation<const P extends Paths<D, Extract<T, string>>>(\n\t\tparent: GenericId<Tables[T]['tableName']>,\n\t\tpath: P,\n\t): Omit<\n\t\tTableQuery<\n\t\t\tTables,\n\t\t\tD,\n\t\t\tExtract<PathTarget<D, Extract<T, string>, P>, keyof Tables>,\n\t\t\t{},\n\t\t\tRemainingDepth<P, L>\n\t\t>,\n\t\t'relation'\n\t>\n\tpaginate(args: EngineArgs<Tables, D, T>): Promise<PaginationResult<RelationRow<Tables, D, T, S>>>\n}\nexport interface QueryEngineDeclaration<\n\tSchema extends SchemaDefinition<GenericSchema, boolean>,\n\tTables extends RelationTables,\n\tD extends RelationDeclarations,\n> {\n\tschema: Schema\n\trelations: RelationRegistry<Tables, D>\n}\n/** Advanced server configuration, separate from the normal schema/relations declaration. */\nexport interface QueryEngineOptions<Schema extends SchemaDefinition<GenericSchema, boolean>> {\n\t/** Explicit codecs validate at creation. Otherwise environment keys load at first paginate. */\n\tcursor?: CursorCodecConfig\n\tbudgets?: Partial<ManagedBudgets>\n\tmaxPageSize?: number\n\tpolicyVersion?: string\n\tengineVersion?: string\n\ttrustedAdapter?: ManagedSecurityConfig<DataModelFromSchemaDefinition<Schema>>['trustedAdapter']\n}\n\n/** @deprecated Use QueryEngineDeclaration and optional QueryEngineOptions separately. */\nexport interface QueryEngineConfig<\n\tSchema extends SchemaDefinition<GenericSchema, boolean>,\n\tTables extends RelationTables,\n\tD extends RelationDeclarations,\n>\n\textends QueryEngineDeclaration<Schema, Tables, D>, QueryEngineOptions<Schema> {\n\tcursor: CursorCodecConfig\n}\n\ntype QueryEngine<\n\tSchema extends SchemaDefinition<GenericSchema, boolean>,\n\tTables extends RelationTables,\n\tD extends RelationDeclarations,\n> = {\n\t(ctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>): {\n\t\t[T in keyof Tables]: TableQuery<Tables, D, T>\n\t}\n\ttables: RelationRegistry<Tables, D>['tables']\n}\n\n/** Central facade. Endpoint chains carry declarative constraints and explicit relationships. */\nexport function createQueryEngine<\n\tSchema extends SchemaDefinition<GenericSchema, boolean>,\n\tconst Tables extends RelationTables,\n\tconst D extends RelationDeclarations,\n>(\n\tconfig: QueryEngineDeclaration<Schema, Tables, D>,\n\toptions?: QueryEngineOptions<Schema>,\n): QueryEngine<Schema, Tables, D>\n/** @deprecated Pass advanced fields in the optional second argument. */\nexport function createQueryEngine<\n\tSchema extends SchemaDefinition<GenericSchema, boolean>,\n\tconst Tables extends RelationTables,\n\tconst D extends RelationDeclarations,\n>(\n\tconfig: QueryEngineDeclaration<Schema, Tables, D> &\n\t\tQueryEngineOptions<Schema> & { cursor: CursorCodecConfig },\n): QueryEngine<Schema, Tables, D>\nexport function createQueryEngine<\n\tSchema extends SchemaDefinition<GenericSchema, boolean>,\n\tconst Tables extends RelationTables,\n\tconst D extends RelationDeclarations,\n>(\n\tconfig: QueryEngineDeclaration<Schema, Tables, D> & QueryEngineOptions<Schema>,\n\toptions?: QueryEngineOptions<Schema>,\n) {\n\tconst { schema, relations } = config\n\t// Retain released one-argument calls; a second argument overrides legacy fields.\n\tconst settings = { ...config, ...options }\n\tconst planner = createPlanner(schema, relations)\n\tlet cursorCodec = settings.cursor === undefined ? undefined : createCursorCodec(settings.cursor)\n\tconst maxPageSize = settings.maxPageSize ?? 100\n\tif (!Number.isSafeInteger(maxPageSize) || maxPageSize <= 0 || maxPageSize > 100)\n\t\tthrow new Error('Invalid central page size')\n\tconst engineVersion = settings.engineVersion ?? 'scalar-1'\n\tconst policyVersion = settings.policyVersion ?? '1'\n\t// Bind the full immutable identity through a collision-resistant digest so\n\t// real application registries fit the codec's bounded AAD. Live guards still\n\t// inspect current schemas; this caches only the immutable version identity.\n\tlet registryDigest: string | undefined\n\tconst security = createManagedSecurity<DataModelFromSchemaDefinition<Schema>>({\n\t\ttables: Object.keys(relations.tables),\n\t\tpolicyVersion,\n\t\tbudgets: settings.budgets,\n\t\ttrustedAdapter: settings.trustedAdapter,\n\t})\n\tfunction querying(ctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>) {\n\t\tplanner.assertUnchanged()\n\t\tconst request = security(ctx)\n\t\tfunction tableQuery(\n\t\t\ttable: string,\n\t\t\tconditions: readonly IndexCondition[] = [],\n\t\t\tconstraints: readonly StoredConstraint[] = [],\n\t\t\tselection: OutputSelection = {},\n\t\t\troute?: { table: string; parent: string; path: string[] },\n\t\t) {\n\t\t\treturn Object.freeze({\n\t\t\t\twhen: (value: IndexCondition['value'], index: string) =>\n\t\t\t\t\ttableQuery(table, [...conditions, { value, index }], constraints, selection, route),\n\t\t\t\twhere: (spec: StoredConstraint) =>\n\t\t\t\t\ttableQuery(table, conditions, [...constraints, spec], selection, route),\n\t\t\t\twith: (selected: OutputSelection) =>\n\t\t\t\t\ttableQuery(table, conditions, constraints, selected, route),\n\t\t\t\trelation: (parent: string, declaredPath: string) => {\n\t\t\t\t\tconst path = declaredPath.split('.')\n\t\t\t\t\tif (!path.length || path.length > 4) throw new Error('INVALID_RELATION')\n\t\t\t\t\tlet target = table\n\t\t\t\t\tfor (const name of path) {\n\t\t\t\t\t\tconst edge = planner.metadata.declarations[target]?.[name]\n\t\t\t\t\t\tif (!edge) throw new Error('INVALID_RELATION')\n\t\t\t\t\t\ttarget = edge.target\n\t\t\t\t\t}\n\t\t\t\t\treturn tableQuery(target, conditions, constraints, {}, { table, parent, path })\n\t\t\t\t},\n\t\t\t\tasync paginate(input: QueryInput) {\n\t\t\t\t\t// Resolve once per global engine, before any native query or policy reads.\n\t\t\t\t\tconst codec = (cursorCodec ??= createEnvironmentCursorCodec())\n\t\t\t\t\tplanner.assertUnchanged()\n\t\t\t\t\tconst pageSchema = planner.page(table, selection)\n\t\t\t\t\tassertRelationshipDepth(selection, route?.path.length ?? 0)\n\t\t\t\t\tconst plan = planner.plan(table, input, route ? [] : conditions, constraints)\n\t\t\t\t\tif (plan.args.numItems > maxPageSize) throw new Error('Page size exceeds central ceiling')\n\t\t\t\t\t// Native root limits stay native; lowering those limits cannot raise\n\t\t\t\t\t// the independent shared request meter.\n\t\t\t\t\tplan.args.maximumRowsRead = Math.min(\n\t\t\t\t\t\tplan.args.maximumRowsRead ?? request.budgets.rawCandidates,\n\t\t\t\t\t\trequest.budgets.rawCandidates,\n\t\t\t\t\t)\n\t\t\t\t\tplan.args.maximumBytesRead = Math.min(\n\t\t\t\t\t\tplan.args.maximumBytesRead ?? request.budgets.documentBytes,\n\t\t\t\t\t\trequest.budgets.documentBytes,\n\t\t\t\t\t)\n\t\t\t\t\tregistryDigest ??= Array.from(\n\t\t\t\t\t\tnew Uint8Array(\n\t\t\t\t\t\t\tawait crypto.subtle.digest('SHA-256', new TextEncoder().encode(relations.identity)),\n\t\t\t\t\t\t),\n\t\t\t\t\t\t(byte) => byte.toString(16).padStart(2, '0'),\n\t\t\t\t\t).join('')\n\t\t\t\t\t// The digest await is a boundary too: mutation must be detected\n\t\t\t\t\t// before authenticated cursor validation or native query construction.\n\t\t\t\t\tplanner.assertUnchanged()\n\t\t\t\t\t// SAFETY: selection has passed the registry page validator before binding.\n\t\t\t\t\tconst binding: CursorBinding = {\n\t\t\t\t\t\ttable: route?.table ?? table,\n\t\t\t\t\t\tindex: plan.index?.name ?? 'by_creation_time',\n\t\t\t\t\t\torder: plan.args.order ?? 'asc',\n\t\t\t\t\t\tclientFilters: plan.clientIdentity,\n\t\t\t\t\t\t// Include output selection and the complete immutable scope in trusted identity.\n\t\t\t\t\t\tserverFilters: {\n\t\t\t\t\t\t\tpredicates: plan.serverIdentity,\n\t\t\t\t\t\t\tselection: selection as import('convex/values').Value,\n\t\t\t\t\t\t\tscope: { ...request.scope },\n\t\t\t\t\t\t},\n\t\t\t\t\t\trelationPath: [],\n\t\t\t\t\t\tprincipal: JSON.stringify(request.scope),\n\t\t\t\t\t\ttenant: request.scope.tenant,\n\t\t\t\t\t\tpolicyVersion,\n\t\t\t\t\t\tengineVersion,\n\t\t\t\t\t\tregistryVersion: registryDigest,\n\t\t\t\t\t}\n\t\t\t\t\t// The executor accepts registry-selected native tables, rather than a static DM key.\n\t\t\t\t\tconst executorRequest: unknown = request\n\t\t\t\t\t// SAFETY: only native DM types are erased at this private executor boundary.\n\t\t\t\t\tconst executor = relationshipExecutor(\n\t\t\t\t\t\tschema,\n\t\t\t\t\t\texecutorRequest as ExecutionRequest,\n\t\t\t\t\t\tplanner,\n\t\t\t\t\t\tcodec,\n\t\t\t\t\t\tbinding,\n\t\t\t\t\t)\n\t\t\t\t\tif (route)\n\t\t\t\t\t\treturn executor.continuation(\n\t\t\t\t\t\t\troute.table,\n\t\t\t\t\t\t\troute.parent,\n\t\t\t\t\t\t\troute.path,\n\t\t\t\t\t\t\tselection,\n\t\t\t\t\t\t\tplan.args,\n\t\t\t\t\t\t\tconditions,\n\t\t\t\t\t\t\tconstraints,\n\t\t\t\t\t\t)\n\t\t\t\t\tconst result = await executeQuery(\n\t\t\t\t\t\tschema,\n\t\t\t\t\t\trequest,\n\t\t\t\t\t\ttable,\n\t\t\t\t\t\tplan,\n\t\t\t\t\t\tplanner,\n\t\t\t\t\t\tcodec,\n\t\t\t\t\t\tbinding,\n\t\t\t\t\t\tasync (row, permitted) => {\n\t\t\t\t\t\t\t// SAFETY: metered native stream rows contain the validated system fields.\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!permitted ||\n\t\t\t\t\t\t\t\t!(await executor.matches(table, row as RelationDocument, plan.residual))\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t\t// SAFETY: the same native row is authorized and matches the declared predicates.\n\t\t\t\t\t\t\treturn executor.output(table, row as RelationDocument, selection, plan.args)\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t\tplanner.assertUnchanged()\n\t\t\t\t\treturn pageSchema.parse(result)\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\t// SAFETY: each key comes from the exact validated registry; the planner\n\t\t// validates all input and projection while mapped types retain table brands.\n\t\treturn Object.freeze(\n\t\t\tObject.fromEntries(Object.keys(relations.tables).map((table) => [table, tableQuery(table)])),\n\t\t) as { [T in keyof Tables]: TableQuery<Tables, D, T> }\n\t}\n\treturn Object.assign(querying, { tables: relations.tables })\n}\n"],"mappings":"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"}
|
package/dist/relations.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{nativePagination as e,relationFilterSchema as t,scalarPredicateSchema as n}from"./querying/filters.mjs";import{paginated as r}from"./zod-table.mjs";import{zodToConvex as i}from"convex-helpers/server/zod4";import{z as a}from"zod";import{v as o}from"convex/values";const s=Symbol(`relationField`),c=Symbol(`relationEdge`),l=/* @__PURE__ */ new WeakMap,u=/* @__PURE__ */ new WeakMap,d=/* @__PURE__ */ new WeakMap,f=/* @__PURE__ */ new WeakSet,p=/* @__PURE__ */ new WeakSet,m=a.object({indexes:a.array(a.object({indexDescriptor:a.string(),fields:a.array(a.string())}))});function h(n,o){let c={},h=/* @__PURE__ */ new Set,g={},_={},v={};for(let[e,t]of Object.entries(n)){if(t.tableName!==e||[`one`,`many`,`__proto__`,`constructor`,`prototype`].includes(e))throw Error(`Invalid relation table "${e}"`);let n=t.dto(!0);if(!(n instanceof a.ZodObject))throw Error(`Expected object DTO`);let r=I(n),o=i(t.schema),l=k(t.table);M(l.documentType),N(e,o.fields,l.documentType);let u=m.parse(l).indexes.map(e=>({name:e.indexDescriptor,fields:[...e.fields]})).sort((e,t)=>e.name.localeCompare(t.name));M(A(i(n))),c[e]={fields:o.fields,indexes:u,dto:n,assertUnchanged:r};let p=Object.create(null);for(let[t,n]of Object.entries(o.fields)){let r={[s]:void 0,table:e,field:t,through(e){let t=d.get(e);if(!h.has(e)||!t||!S(n,t))throw Error(`Invalid junction field or registry`);let i=Object.freeze({base:r,junction:e});return f.add(i),i}};d.set(r,n),h.add(r),p[t]=Object.freeze(r)}g[e]=Object.freeze(p),_[e]=t=>x(e,`one`,t,h),v[e]=t=>x(e,`many`,t,h)}let C=o(Object.freeze({...g,one:Object.freeze(_),many:Object.freeze(v)})),T=Object.fromEntries(Object.entries(C).map(([e,t])=>[e,Object.freeze({...t})]));Object.freeze(T);for(let[e,t]of Object.entries(T)){if(!Object.hasOwn(n,e))throw Error(`Unknown source table "${e}"`);for(let[n,r]of Object.entries(t)){if(Object.hasOwn(c[e].fields,n)||[`and`,`or`,`not`,`fields`,`then`,`__proto__`,`constructor`,`prototype`].includes(n))throw Error(`Relation name collision "${n}"`);if(!p.has(r)||!h.has(b(r.from))||!h.has(b(r.to))||r.source!==e)throw Error(`Relation source bucket mismatch`);w(r,c)}}let P={metadata:c,declarations:T},F={};for(let i of Object.keys(n))F[i]={paginationOpts(n){return B(P),y(n),H(e.extend({filters:t(D(P,i,n)).optional(),order:a.enum([`asc`,`desc`]).optional()}),()=>B(P))},pageDto(e){return B(P),y(e),H(r(O(P,i,e)).result,()=>B(P))}};let L=JSON.stringify([1,Object.entries(c).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,Object.entries(t.fields).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,t.isOptional,A(t)]),t.indexes,j(A(i(t.dto)))]),Object.entries(T).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,Object.entries(t).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,t.cardinality,t.target,E(t.from),E(t.to)])])]),R=Object.freeze({identity:L,version:1,tables:Object.freeze(F)});l.set(R,U(P));let V=Object.freeze({...n}),W=Object.fromEntries(Object.entries(V).map(([e,t])=>{let n=z(t.schema,`stored`),r=JSON.stringify(k(t.table));return[e,()=>{if(n(),JSON.stringify(k(t.table))!==r)throw Error(`Native schema mutated after registry construction: ${e}`)}]}));return u.set(R,{internal:P,tables:V,assertStoredUnchanged:()=>{for(let e of Object.values(W))e()},assertNativeTable(e,t){if(!Object.hasOwn(V,e)||V[e].table!==t)throw Error(`Native schema/table ownership disagrees with relation registry`);W[e]()}}),R}function g(i){let o=u.get(i);if(!o)throw Error(`Unknown relation registry`);let s=()=>{B(o.internal),o.assertStoredUnchanged()},c=e=>{s();let t=Object.keys(o.tables);if(t.length!==Object.keys(e).length||t.some(t=>e[t]!==o.tables[t].table))throw Error(`Native schema/table ownership disagrees with relation registry`)};return s(),Object.freeze({assertUnchanged:s,assertNativeTable:o.assertNativeTable,assertSchema:c,pagination(r){s();let i=o.internal,c=/* @__PURE__ */ new Map;function l(e,t){let r=i.metadata[e];if(!r)throw Error(`Unknown query table`);let a=c.get(e);if(!a){a={};for(let[e,t]of Object.entries(r.fields))v(t)&&(a[e]=n(t).optional());c.set(e,a)}return{fields:a,relations:t>=4?{}:Object.fromEntries(Object.entries(i.declarations[e]??{}).map(([e,n])=>[e,{cardinality:n.cardinality,target:l(n.target,t+1)}]))}}return e.extend({filters:t(l(r,0)).optional(),order:a.enum([`asc`,`desc`]).optional()})},page(e,t){return s(),y(t),r(O(o.internal,e,t)).result},project(e,t,n,r){if(c(e),!r())return null;let i=o.internal.metadata[t]?.dto;if(!i)throw Error(`Unknown query table`);return i.parse(Object.fromEntries(Object.keys(i.shape).filter(e=>Object.hasOwn(n,e)).map(e=>[e,n[e]])))}})}function _(e){let t=l.get(e);if(!t)throw Error(`Unknown relation registry`);return t}function v(e){return[`id`,`string`,`float64`,`int64`,`boolean`,`null`,`literal`].includes(e.kind)||e.kind===`union`&&e.members.every(v)}function y(e){let t=[{value:e,depth:0,ancestors:[]}],n=0;for(;t.length;){let{value:e,depth:r,ancestors:i}=t.pop();if(++n>256||r>4||i.includes(e))throw Error(`Selection cycle or depth/node budget exceeded`);let a=Object.getPrototypeOf(e);if(a!==Object.prototype&&a!==null)throw Error(`Invalid selection`);let o=Reflect.ownKeys(e);if(o.length+n+t.length>256)throw Error(`Selection node budget exceeded`);for(let a of o){let o=Object.getOwnPropertyDescriptor(e,a);if(!o.enumerable||!(`value`in o)||String(a)!==a)throw Error(`Invalid selection property`);if(o.value!==!0)t.push({value:o.value,depth:r+1,ancestors:[...i,e]});else if(++n>256)throw Error(`Selection node budget exceeded`)}}}function b(e){return`base`in e?e.base:e}function x(e,t,n,r){for(let e of[n.from,n.to])if(!r.has(b(e))||`base`in e&&(!f.has(e)||!r.has(e.junction)))throw Error(`Foreign registry or invalid field reference`);if(b(n.to).table!==e)throw Error(`Wrong relation target table`);let i=Object.freeze({[c]:!0,source:b(n.from).table,target:e,cardinality:t,from:n.from,to:n.to});return p.add(i),i}function S(e,t){let n=C(e);return n.length>0&&JSON.stringify(n)===JSON.stringify(C(t))}function C(e){if(e.kind===`union`)return[...new Set(e.members.flatMap(C))].sort();if(e.kind===`null`)return[];if(![`id`,`string`,`float64`,`int64`,`boolean`,`literal`].includes(e.kind))throw Error(`Unsupported relation field kind`);return[JSON.stringify(A(e))]}function w(e,t){let n=e.from,r=e.to;if(`base`in n!=`base`in r)throw Error(`Both relation legs must use through`);if(`base`in n&&`base`in r){if(n.junction.table!==r.junction.table)throw Error(`Different junction tables`);T(t,n.junction.table,n.junction.field),T(t,e.target,r.base.field);return}if(!S(d.get(b(n)),d.get(b(r))))throw Error(`Incompatible relation field types`);T(t,e.target,b(r).field)}function T(e,t,n){if(n!==`_id`&&!e[t].indexes.some(e=>e.fields[0]===n))throw Error(`No active index begins with "${t}.${n}"`)}function E(e){return`base`in e?[e.base.table,e.base.field,e.junction.table,e.junction.field]:[e.table,e.field]}function D(e,t,r,i=0){let a={},o={};for(let[s,c]of Object.entries(r)){let r=e.metadata[t].fields[s],l=e.declarations[t]?.[s];if(c===!0&&r)a[s]=n(r).optional();else if(c!==!0&&l&&i<4)o[s]={cardinality:l.cardinality,target:D(e,l.target,c,i+1)};else throw Error(`Invalid relation filter selection "${t}.${s}"`)}return{fields:a,relations:o}}function O(e,t,n,i=0){let o={};for(let[a,s]of Object.entries(n)){let n=e.declarations[t]?.[a];if(!n||i>=4)throw Error(`Unknown or too deep relation "${t}.${a}"`);let c=s===!0?e.metadata[n.target].dto:O(e,n.target,s,i+1);o[a]=n.cardinality===`one`?c.nullable():r(c).result}let s=e.metadata[t].dto;if(!(s instanceof a.ZodObject))throw Error(`Expected object DTO`);return H(s.extend(o).strict(),()=>B(e))}function k(e){return e.export()}function A(e){return e.json}function j(e){if(Array.isArray(e))return`[${e.map(j).join(`,`)}]`;if(e!==null&&Object(e)===e){let t=e;return`{${Object.keys(t).sort().map(e=>`${JSON.stringify(e)}:${j(t[e])}`).join(`,`)}}`}return JSON.stringify(e)}function M(e){if(e===null||Object(e)!==e)return;let t=e;if(t.type===`any`)throw Error(`Unsupported native validation form`);for(let e of Object.values(t))M(e)}function N(e,t,n){let{_id:r,_creationTime:i,...a}=t;if(!r||!i||j(A(r))!==j(A(o.id(e)))||j(A(i))!==j(A(o.number()))||r.isOptional!==`required`||i.isOptional!==`required`||j(n)!==j(A(o.object(a))))throw Error(`Native stored document disagrees with boundary "${e}"`)}const P=a.object({field:a.string()}),F=new Set([P,P.pick({field:!0}),P.omit({field:!0}),P.extend({other:a.string()}),P.safeExtend({other:a.string()}),P.merge(a.object({other:a.string()})),P.partial(),P.required()].flatMap(e=>{let t=Object.getOwnPropertyDescriptor(e._zod.def,`shape`)?.get;return t?[Function.prototype.toString.call(t)]:[]}));function I(e){return z(e,`dto`)}const L=Function.prototype.toString.call(Object.getOwnPropertyDescriptor(a.string().default(``)._zod.def,`defaultValue`).get),R=Function.prototype.toString.call(Object.getOwnPropertyDescriptor(a.string().prefault(``)._zod.def,`defaultValue`).get);function z(e,t){let n=[],r=/* @__PURE__ */ new Set,i=[],o=/* @__PURE__ */ new Set,s=/* @__PURE__ */ new Set,c=[];function l(e,u){if(s.has(e))throw Error(`Unsupported cyclic DTO schema`);if(!o.has(e)){if(u>32||n.length>=4096)throw Error(`DTO schema graph budget exceeded`);o.add(e),s.add(e);try{if(e instanceof RegExp&&i.push({expression:e,source:e.source,flags:e.flags}),e instanceof a.core.$ZodCheck&&`check`in e._zod.def){if(t===`dto`&&e._zod.def.check===`overwrite`)throw Error(`Unsupported DTO overwrite check`);l(e._zod.def,u+1),n.push({object:e._zod,exactKeys:!1,keys:[`def`,`check`],values:[`def`,`check`].map(t=>Object.getOwnPropertyDescriptor(e._zod,t))});return}if(e instanceof a.core.$ZodType){let i=e._zod.def;if(t===`dto`&&(![`object`,`array`,`optional`,`nullable`,`nonoptional`,`union`,`intersection`,`tuple`,`record`,`string`,`number`,`boolean`,`bigint`,`null`,`never`,`literal`,`enum`].includes(i.type)||`coerce`in i&&i.coerce))throw Error(`Unsupported DTO schema "${i.type}"`);if(t===`stored`&&(i.type==="default"||i.type===`prefault`)){let e=Object.getOwnPropertyDescriptor(i,`defaultValue`);if(e?.get){if(e.set||Function.prototype.toString.call(e.get)!==(i.type==="default"?L:R))throw Error(`Unsupported stored schema accessor`);r.add(i)}}if(i.type===`object`){let t=Object.getOwnPropertyDescriptor(i,`shape`);if(t?.get){if(t.set||!F.has(Function.prototype.toString.call(t.get)))throw Error(`Unsupported DTO schema accessor`);let e=i.shape;l(e,u+1),Object.getOwnPropertyDescriptor(i,`shape`)?.get&&c.push({definition:i,fields:e})}if(e instanceof a.ZodObject){let t=Object.getOwnPropertyDescriptor(e,`shape`);if(t&&!(`value`in t))throw Error(`Unsupported DTO schema accessor`);l(e.shape,u+1)}}l(i,u+1),n.push({object:e._zod,exactKeys:!1,keys:[`def`],values:[Object.getOwnPropertyDescriptor(e._zod,`def`)]});return}let o=Reflect.ownKeys(e),s=o.map(t=>Object.getOwnPropertyDescriptor(e,t));if(s.some((t,n)=>!(`value`in t)&&!c.some(({definition:t})=>t===e&&o[n]===`shape`)&&!(r.has(e)&&o[n]===`defaultValue`)))throw Error(`Unsupported DTO schema accessor`);n.push({object:e,exactKeys:!0,keys:o,values:s});for(let e of s)e.value!==null&&Object(e.value)===e.value&&!(e.value instanceof Function)&&l(e.value,u+1)}finally{s.delete(e)}}}return l(e,0),()=>{for(let e of i)if(e.expression.source!==e.source||e.expression.flags!==e.flags)throw Error(`DTO schema mutated after registry construction`);for(let e of n){let t=e.keys;if(e.exactKeys&&Reflect.ownKeys(e.object).length!==t.length)throw Error(`DTO schema mutated after registry construction`);for(let n=0;n<t.length;n++){if(e.object instanceof RegExp&&t[n]===`lastIndex`)continue;let r=Object.getOwnPropertyDescriptor(e.object,t[n]),i=e.values[n];if(!r||r.value!==i.value||r.get!==i.get||r.set!==i.set||r.enumerable!==i.enumerable)throw Error(`DTO schema mutated after registry construction`)}}for(let{definition:e,fields:t}of c){let n=e.shape,r=Reflect.ownKeys(t);if(Reflect.ownKeys(n).length!==r.length||r.some(e=>Object.getOwnPropertyDescriptor(n,e)?.value!==Object.getOwnPropertyDescriptor(t,e)?.value))throw Error(`DTO schema mutated after registry construction`)}}}function B(e){for(let t of Object.values(e.metadata))t.assertUnchanged()}const V=a.core.$constructor(`GuardedDtoObject`,(e,t)=>{let n=t;a.ZodObject.init(e,t),e._zod.deferred??=[],e._zod.deferred.push(()=>{let t=e._zod.run;e._zod.run=(e,r)=>{try{n.assertUnchanged()}catch{return e.issues.push({code:`custom`,input:e.value,message:`DTO schema mutated after registry construction`}),e}return t(e,r)}})});function H(e,t){let n=e.shape,r={...e._zod.def,assertUnchanged:t};return Object.defineProperty(r,"shape",{enumerable:!0,configurable:!0,get:()=>(t(),n)}),new V(r)}function U(e){let t={};for(let[n,r]of Object.entries(e.metadata)){let e={};for(let[t,n]of Object.entries(r.fields))e[t]=Object.freeze({kind:n.kind,optional:n.isOptional===`optional`,nullable:W(n),idTables:Object.freeze(G(n)),native:K(A(n))});t[n]=Object.freeze({fields:Object.freeze(e),indexes:Object.freeze(r.indexes.map(e=>Object.freeze({name:e.name,fields:Object.freeze([...e.fields])})))})}return Object.freeze({tables:Object.freeze(t),declarations:e.declarations})}function W(e){return e.kind===`null`||e.kind===`union`&&e.members.some(W)}function G(e){return e.kind===`id`?[e.tableName]:e.kind===`union`?[...new Set(e.members.flatMap(G))].sort():[]}function K(e){let t=structuredClone(e),n=[t];for(;n.length;){let e=n.pop();if(Object(e)!==e)continue;let t=e;Object.freeze(t),n.push(...Object.values(t))}return t}export{h as defineRelations,g as getRelationExecutor,_ as getRelationMetadata};
|
|
1
|
+
import{nativePagination as e,relationFilterSchema as t,scalarPredicateSchema as n}from"./querying/filters.mjs";import{paginated as r}from"./zod-table.mjs";import{zodToConvex as i}from"convex-helpers/server/zod4";import{z as a}from"zod";import{v as o}from"convex/values";const s=Symbol(`relationField`),c=Symbol(`relationEdge`),l=/* @__PURE__ */ new WeakMap,u=/* @__PURE__ */ new WeakMap,d=/* @__PURE__ */ new WeakMap,f=/* @__PURE__ */ new WeakSet,p=/* @__PURE__ */ new WeakSet,m=a.object({indexes:a.array(a.object({indexDescriptor:a.string(),fields:a.array(a.string())}))});function h(n,o){let c={},h=/* @__PURE__ */ new Set,g={},_={},v={};for(let[e,t]of Object.entries(n)){if(t.schema instanceof a.ZodUnion)throw Error(`Variant tables are not supported by relations or the query engine`);if(t.tableName!==e||[`one`,`many`,`__proto__`,`constructor`,`prototype`].includes(e))throw Error(`Invalid relation table "${e}"`);let n=t.dto(!0);if(!(n instanceof a.ZodObject))throw Error(`Expected object DTO`);let r=I(n),o=i(t.schema),l=k(t.table);M(l.documentType),N(e,o.fields,l.documentType);let u=m.parse(l).indexes.map(e=>({name:e.indexDescriptor,fields:[...e.fields]})).sort((e,t)=>e.name.localeCompare(t.name));M(A(i(n))),c[e]={fields:o.fields,indexes:u,dto:n,assertUnchanged:r};let p=Object.create(null);for(let[t,n]of Object.entries(o.fields)){let r={[s]:void 0,table:e,field:t,through(e){let t=d.get(e);if(!h.has(e)||!t||!S(n,t))throw Error(`Invalid junction field or registry`);let i=Object.freeze({base:r,junction:e});return f.add(i),i}};d.set(r,n),h.add(r),p[t]=Object.freeze(r)}g[e]=Object.freeze(p),_[e]=t=>x(e,`one`,t,h),v[e]=t=>x(e,`many`,t,h)}let C=o(Object.freeze({...g,one:Object.freeze(_),many:Object.freeze(v)})),T=Object.fromEntries(Object.entries(C).map(([e,t])=>[e,Object.freeze({...t})]));Object.freeze(T);for(let[e,t]of Object.entries(T)){if(!Object.hasOwn(n,e))throw Error(`Unknown source table "${e}"`);for(let[n,r]of Object.entries(t)){if(Object.hasOwn(c[e].fields,n)||[`and`,`or`,`not`,`fields`,`then`,`__proto__`,`constructor`,`prototype`].includes(n))throw Error(`Relation name collision "${n}"`);if(!p.has(r)||!h.has(b(r.from))||!h.has(b(r.to))||r.source!==e)throw Error(`Relation source bucket mismatch`);w(r,c)}}let P={metadata:c,declarations:T},F={};for(let i of Object.keys(n))F[i]={paginationOpts(n){return B(P),y(n),H(e.extend({filters:t(D(P,i,n)).optional(),order:a.enum([`asc`,`desc`]).optional()}),()=>B(P))},pageDto(e){return B(P),y(e),H(r(O(P,i,e)).result,()=>B(P))}};let L=JSON.stringify([1,Object.entries(c).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,Object.entries(t.fields).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,t.isOptional,A(t)]),t.indexes,j(A(i(t.dto)))]),Object.entries(T).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,Object.entries(t).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,t.cardinality,t.target,E(t.from),E(t.to)])])]),R=Object.freeze({identity:L,version:1,tables:Object.freeze(F)});l.set(R,U(P));let V=Object.freeze({...n}),W=Object.fromEntries(Object.entries(V).map(([e,t])=>{let n=z(t.schema,`stored`),r=JSON.stringify(k(t.table));return[e,()=>{if(n(),JSON.stringify(k(t.table))!==r)throw Error(`Native schema mutated after registry construction: ${e}`)}]}));return u.set(R,{internal:P,tables:V,assertStoredUnchanged:()=>{for(let e of Object.values(W))e()},assertNativeTable(e,t){if(!Object.hasOwn(V,e)||V[e].table!==t)throw Error(`Native schema/table ownership disagrees with relation registry`);W[e]()}}),R}function g(i){let o=u.get(i);if(!o)throw Error(`Unknown relation registry`);let s=()=>{B(o.internal),o.assertStoredUnchanged()},c=e=>{s();let t=Object.keys(o.tables);if(t.length!==Object.keys(e).length||t.some(t=>e[t]!==o.tables[t].table))throw Error(`Native schema/table ownership disagrees with relation registry`)};return s(),Object.freeze({assertUnchanged:s,assertNativeTable:o.assertNativeTable,assertSchema:c,pagination(r){s();let i=o.internal,c=/* @__PURE__ */ new Map;function l(e,t){let r=i.metadata[e];if(!r)throw Error(`Unknown query table`);let a=c.get(e);if(!a){a={};for(let[e,t]of Object.entries(r.fields))v(t)&&(a[e]=n(t).optional());c.set(e,a)}return{fields:a,relations:t>=4?{}:Object.fromEntries(Object.entries(i.declarations[e]??{}).map(([e,n])=>[e,{cardinality:n.cardinality,target:l(n.target,t+1)}]))}}return e.extend({filters:t(l(r,0)).optional(),order:a.enum([`asc`,`desc`]).optional()})},page(e,t){return s(),y(t),r(O(o.internal,e,t)).result},project(e,t,n,r){if(c(e),!r())return null;let i=o.internal.metadata[t]?.dto;if(!i)throw Error(`Unknown query table`);return i.parse(Object.fromEntries(Object.keys(i.shape).filter(e=>Object.hasOwn(n,e)).map(e=>[e,n[e]])))}})}function _(e){let t=l.get(e);if(!t)throw Error(`Unknown relation registry`);return t}function v(e){return[`id`,`string`,`float64`,`int64`,`boolean`,`null`,`literal`].includes(e.kind)||e.kind===`union`&&e.members.every(v)}function y(e){let t=[{value:e,depth:0,ancestors:[]}],n=0;for(;t.length;){let{value:e,depth:r,ancestors:i}=t.pop();if(++n>256||r>4||i.includes(e))throw Error(`Selection cycle or depth/node budget exceeded`);let a=Object.getPrototypeOf(e);if(a!==Object.prototype&&a!==null)throw Error(`Invalid selection`);let o=Reflect.ownKeys(e);if(o.length+n+t.length>256)throw Error(`Selection node budget exceeded`);for(let a of o){let o=Object.getOwnPropertyDescriptor(e,a);if(!o.enumerable||!(`value`in o)||String(a)!==a)throw Error(`Invalid selection property`);if(o.value!==!0)t.push({value:o.value,depth:r+1,ancestors:[...i,e]});else if(++n>256)throw Error(`Selection node budget exceeded`)}}}function b(e){return`base`in e?e.base:e}function x(e,t,n,r){for(let e of[n.from,n.to])if(!r.has(b(e))||`base`in e&&(!f.has(e)||!r.has(e.junction)))throw Error(`Foreign registry or invalid field reference`);if(b(n.to).table!==e)throw Error(`Wrong relation target table`);let i=Object.freeze({[c]:!0,source:b(n.from).table,target:e,cardinality:t,from:n.from,to:n.to});return p.add(i),i}function S(e,t){let n=C(e);return n.length>0&&JSON.stringify(n)===JSON.stringify(C(t))}function C(e){if(e.kind===`union`)return[...new Set(e.members.flatMap(C))].sort();if(e.kind===`null`)return[];if(![`id`,`string`,`float64`,`int64`,`boolean`,`literal`].includes(e.kind))throw Error(`Unsupported relation field kind`);return[JSON.stringify(A(e))]}function w(e,t){let n=e.from,r=e.to;if(`base`in n!=`base`in r)throw Error(`Both relation legs must use through`);if(`base`in n&&`base`in r){if(n.junction.table!==r.junction.table)throw Error(`Different junction tables`);T(t,n.junction.table,n.junction.field),T(t,e.target,r.base.field);return}if(!S(d.get(b(n)),d.get(b(r))))throw Error(`Incompatible relation field types`);T(t,e.target,b(r).field)}function T(e,t,n){if(n!==`_id`&&!e[t].indexes.some(e=>e.fields[0]===n))throw Error(`No active index begins with "${t}.${n}"`)}function E(e){return`base`in e?[e.base.table,e.base.field,e.junction.table,e.junction.field]:[e.table,e.field]}function D(e,t,r,i=0){let a={},o={};for(let[s,c]of Object.entries(r)){let r=e.metadata[t].fields[s],l=e.declarations[t]?.[s];if(c===!0&&r)a[s]=n(r).optional();else if(c!==!0&&l&&i<4)o[s]={cardinality:l.cardinality,target:D(e,l.target,c,i+1)};else throw Error(`Invalid relation filter selection "${t}.${s}"`)}return{fields:a,relations:o}}function O(e,t,n,i=0){let o={};for(let[a,s]of Object.entries(n)){let n=e.declarations[t]?.[a];if(!n||i>=4)throw Error(`Unknown or too deep relation "${t}.${a}"`);let c=s===!0?e.metadata[n.target].dto:O(e,n.target,s,i+1);o[a]=n.cardinality===`one`?c.nullable():r(c).result}let s=e.metadata[t].dto;if(!(s instanceof a.ZodObject))throw Error(`Expected object DTO`);return H(s.extend(o).strict(),()=>B(e))}function k(e){return e.export()}function A(e){return e.json}function j(e){if(Array.isArray(e))return`[${e.map(j).join(`,`)}]`;if(e!==null&&Object(e)===e){let t=e;return`{${Object.keys(t).sort().map(e=>`${JSON.stringify(e)}:${j(t[e])}`).join(`,`)}}`}return JSON.stringify(e)}function M(e){if(e===null||Object(e)!==e)return;let t=e;if(t.type===`any`)throw Error(`Unsupported native validation form`);for(let e of Object.values(t))M(e)}function N(e,t,n){let{_id:r,_creationTime:i,...a}=t;if(!r||!i||j(A(r))!==j(A(o.id(e)))||j(A(i))!==j(A(o.number()))||r.isOptional!==`required`||i.isOptional!==`required`||j(n)!==j(A(o.object(a))))throw Error(`Native stored document disagrees with boundary "${e}"`)}const P=a.object({field:a.string()}),F=new Set([P,P.pick({field:!0}),P.omit({field:!0}),P.extend({other:a.string()}),P.safeExtend({other:a.string()}),P.merge(a.object({other:a.string()})),P.partial(),P.required()].flatMap(e=>{let t=Object.getOwnPropertyDescriptor(e._zod.def,`shape`)?.get;return t?[Function.prototype.toString.call(t)]:[]}));function I(e){return z(e,`dto`)}const L=Function.prototype.toString.call(Object.getOwnPropertyDescriptor(a.string().default(``)._zod.def,`defaultValue`).get),R=Function.prototype.toString.call(Object.getOwnPropertyDescriptor(a.string().prefault(``)._zod.def,`defaultValue`).get);function z(e,t){let n=[],r=/* @__PURE__ */ new Set,i=[],o=/* @__PURE__ */ new Set,s=/* @__PURE__ */ new Set,c=[];function l(e,u){if(s.has(e))throw Error(`Unsupported cyclic DTO schema`);if(!o.has(e)){if(u>32||n.length>=4096)throw Error(`DTO schema graph budget exceeded`);o.add(e),s.add(e);try{if(e instanceof RegExp&&i.push({expression:e,source:e.source,flags:e.flags}),e instanceof a.core.$ZodCheck&&`check`in e._zod.def){if(t===`dto`&&e._zod.def.check===`overwrite`)throw Error(`Unsupported DTO overwrite check`);l(e._zod.def,u+1),n.push({object:e._zod,exactKeys:!1,keys:[`def`,`check`],values:[`def`,`check`].map(t=>Object.getOwnPropertyDescriptor(e._zod,t))});return}if(e instanceof a.core.$ZodType){let i=e._zod.def;if(t===`dto`&&(![`object`,`array`,`optional`,`nullable`,`nonoptional`,`union`,`intersection`,`tuple`,`record`,`string`,`number`,`boolean`,`bigint`,`null`,`never`,`literal`,`enum`].includes(i.type)||`coerce`in i&&i.coerce))throw Error(`Unsupported DTO schema "${i.type}"`);if(t===`stored`&&(i.type==="default"||i.type===`prefault`)){let e=Object.getOwnPropertyDescriptor(i,`defaultValue`);if(e?.get){if(e.set||Function.prototype.toString.call(e.get)!==(i.type==="default"?L:R))throw Error(`Unsupported stored schema accessor`);r.add(i)}}if(i.type===`object`){let t=Object.getOwnPropertyDescriptor(i,`shape`);if(t?.get){if(t.set||!F.has(Function.prototype.toString.call(t.get)))throw Error(`Unsupported DTO schema accessor`);let e=i.shape;l(e,u+1),Object.getOwnPropertyDescriptor(i,`shape`)?.get&&c.push({definition:i,fields:e})}if(e instanceof a.ZodObject){let t=Object.getOwnPropertyDescriptor(e,`shape`);if(t&&!(`value`in t))throw Error(`Unsupported DTO schema accessor`);l(e.shape,u+1)}}l(i,u+1),n.push({object:e._zod,exactKeys:!1,keys:[`def`],values:[Object.getOwnPropertyDescriptor(e._zod,`def`)]});return}let o=Reflect.ownKeys(e),s=o.map(t=>Object.getOwnPropertyDescriptor(e,t));if(s.some((t,n)=>!(`value`in t)&&!c.some(({definition:t})=>t===e&&o[n]===`shape`)&&!(r.has(e)&&o[n]===`defaultValue`)))throw Error(`Unsupported DTO schema accessor`);n.push({object:e,exactKeys:!0,keys:o,values:s});for(let e of s)e.value!==null&&Object(e.value)===e.value&&!(e.value instanceof Function)&&l(e.value,u+1)}finally{s.delete(e)}}}return l(e,0),()=>{for(let e of i)if(e.expression.source!==e.source||e.expression.flags!==e.flags)throw Error(`DTO schema mutated after registry construction`);for(let e of n){let t=e.keys;if(e.exactKeys&&Reflect.ownKeys(e.object).length!==t.length)throw Error(`DTO schema mutated after registry construction`);for(let n=0;n<t.length;n++){if(e.object instanceof RegExp&&t[n]===`lastIndex`)continue;let r=Object.getOwnPropertyDescriptor(e.object,t[n]),i=e.values[n];if(!r||r.value!==i.value||r.get!==i.get||r.set!==i.set||r.enumerable!==i.enumerable)throw Error(`DTO schema mutated after registry construction`)}}for(let{definition:e,fields:t}of c){let n=e.shape,r=Reflect.ownKeys(t);if(Reflect.ownKeys(n).length!==r.length||r.some(e=>Object.getOwnPropertyDescriptor(n,e)?.value!==Object.getOwnPropertyDescriptor(t,e)?.value))throw Error(`DTO schema mutated after registry construction`)}}}function B(e){for(let t of Object.values(e.metadata))t.assertUnchanged()}const V=a.core.$constructor(`GuardedDtoObject`,(e,t)=>{let n=t;a.ZodObject.init(e,t),e._zod.deferred??=[],e._zod.deferred.push(()=>{let t=e._zod.run;e._zod.run=(e,r)=>{try{n.assertUnchanged()}catch{return e.issues.push({code:`custom`,input:e.value,message:`DTO schema mutated after registry construction`}),e}return t(e,r)}})});function H(e,t){let n=e.shape,r={...e._zod.def,assertUnchanged:t};return Object.defineProperty(r,"shape",{enumerable:!0,configurable:!0,get:()=>(t(),n)}),new V(r)}function U(e){let t={};for(let[n,r]of Object.entries(e.metadata)){let e={};for(let[t,n]of Object.entries(r.fields))e[t]=Object.freeze({kind:n.kind,optional:n.isOptional===`optional`,nullable:W(n),idTables:Object.freeze(G(n)),native:K(A(n))});t[n]=Object.freeze({fields:Object.freeze(e),indexes:Object.freeze(r.indexes.map(e=>Object.freeze({name:e.name,fields:Object.freeze([...e.fields])})))})}return Object.freeze({tables:Object.freeze(t),declarations:e.declarations})}function W(e){return e.kind===`null`||e.kind===`union`&&e.members.some(W)}function G(e){return e.kind===`id`?[e.tableName]:e.kind===`union`?[...new Set(e.members.flatMap(G))].sort():[]}function K(e){let t=structuredClone(e),n=[t];for(;n.length;){let e=n.pop();if(Object(e)!==e)continue;let t=e;Object.freeze(t),n.push(...Object.values(t))}return t}export{h as defineRelations,g as getRelationExecutor,_ as getRelationMetadata};
|
|
2
2
|
//# sourceMappingURL=relations.mjs.map
|