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.
Files changed (51) hide show
  1. package/README.md +64 -45
  2. package/dist/querying/cursors.d.mts +5 -0
  3. package/dist/querying/cursors.mjs +1 -1
  4. package/dist/querying/cursors.mjs.map +1 -1
  5. package/dist/querying.d.mts +20 -6
  6. package/dist/querying.mjs +1 -1
  7. package/dist/querying.mjs.map +1 -1
  8. package/dist/relations.mjs +1 -1
  9. package/dist/relations.mjs.map +1 -1
  10. package/dist/zod-table.d.mts +59 -2
  11. package/dist/zod-table.mjs +1 -1
  12. package/dist/zod-table.mjs.map +1 -1
  13. package/package.json +10 -3
  14. package/AGENTS.md +0 -47
  15. package/CHANGELOG.md +0 -324
  16. package/ROADMAP.md +0 -126
  17. package/docs/agent-tools.md +0 -41
  18. package/docs/approvals.md +0 -201
  19. package/docs/architecture.md +0 -124
  20. package/docs/auth.md +0 -443
  21. package/docs/command-context.md +0 -150
  22. package/docs/commands-legacy.md +0 -513
  23. package/docs/commands.md +0 -173
  24. package/docs/conventions.md +0 -345
  25. package/docs/crud.md +0 -66
  26. package/docs/domain-contracts.md +0 -148
  27. package/docs/domain-operations.md +0 -615
  28. package/docs/effect.md +0 -403
  29. package/docs/llms.md +0 -324
  30. package/docs/maintainability.md +0 -124
  31. package/docs/migration.md +0 -351
  32. package/docs/oxlint.md +0 -254
  33. package/docs/querying.md +0 -293
  34. package/docs/tenancy.md +0 -176
  35. package/docs/triggers.md +0 -77
  36. package/docs/upgrading.md +0 -287
  37. package/docs/webhooks.md +0 -58
  38. package/docs/zod-table.md +0 -278
  39. package/scripts/convex-config-bridge.mjs +0 -44
  40. package/scripts/convex-smoke-env.mjs +0 -33
  41. package/scripts/extract-changelog.mjs +0 -29
  42. package/scripts/owned-process.mjs +0 -140
  43. package/scripts/owned-process.test.mjs +0 -107
  44. package/scripts/smoke-packed-components.mjs +0 -341
  45. package/scripts/smoke-packed-effect.mjs +0 -439
  46. package/scripts/smoke-packed-querying.mjs +0 -296
  47. package/scripts/test-packed-auth.mjs +0 -72
  48. package/scripts/test-packed-effect.mjs +0 -482
  49. package/scripts/test-packed-ids.mjs +0 -150
  50. package/scripts/test-packed-oxlint.mjs +0 -167
  51. 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 ship with the package — `node_modules/cvx-kit/docs/` when installed,
32
- authored in [`src/docs/`](./src/docs/):
33
-
34
- - [`architecture.md`](./src/docs/architecture.md) — the layered application shape the kit encodes
35
- - [`conventions.md`](./src/docs/conventions.md) — the doctrine: folder structure, file anatomy, naming grammar, import rules
36
- - [`zod-table.md`](./src/docs/zod-table.md) — one zod shape per entity, masked into every boundary
37
- - [`auth.md`](./src/docs/auth.md) — auth-aware function constructors from injected policy
38
- - [`commands.md`](./src/docs/commands.md) — Effect command declarations, lifecycle, and migration
39
- - [`effect.md`](./src/docs/effect.md) — opt-in Effect v4 registries, injected services, and shared API builders
40
- - [`domain-operations.md`](./src/docs/domain-operations.md) — server domain/API examples, contracts, errors, receipts, workflow steps, and selected tools
41
- - [`triggers.md`](./src/docs/triggers.md) — trigger registry, timestamps/append-only/no-delete
42
- - [`approvals.md`](./src/docs/approvals.md) — declarative approval workflows
43
- - [`tenancy.md`](./src/docs/tenancy.md) — row-level security (roles) with opt-in multi-tenant isolation
44
- - [`crud.md`](./src/docs/crud.md) — the CRUD command factory
45
- - [`webhooks.md`](./src/docs/webhooks.md) — the sanctioned webhook boundary
46
- - [`agent-tools.md`](./src/docs/agent-tools.md) — agent tools that are audited commands
47
- - [`upgrading.md`](./src/docs/upgrading.md) — 0.0.x → 0.1.0, old way → new way
48
- - [`maintainability.md`](./src/docs/maintainability.md) — keeping a Convex app maintainable long-term
49
- - [`migration.md`](./src/docs/migration.md) — restructuring an existing raw Convex project into this architecture
50
- - [`llms.md`](./src/docs/llms.md) — condensed reference to paste into an LLM/agent context
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](./src/docs/querying.md),
89
- [DTO parameters](./src/docs/zod-table.md#public-dto-parameters), and
90
- [consumer rule reference](./src/docs/oxlint.md#dto-and-pagination-diagnostics).
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
- `cursor.keys` is a server-only map of key IDs to 64-byte `Uint8Array` secrets.
136
- Keep key material out of arguments, client code, and logs. The default codec
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](./src/docs/commands.md), [complete Effect wiring](./src/docs/effect.md),
330
- and [authenticated builders](./src/docs/auth.md).
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](./src/docs/commands-legacy.md). The Foundation
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`](./src/docs/conventions.md). It targets `convex/`, including
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](./src/docs/oxlint.md)
396
- and the [migration from the initial 0.1.4 plugin](./src/docs/upgrading.md).
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`](./tools/oxlint/README.md). Run `bun run lint` for
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](./CHANGELOG.md) section (extracted by
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
- - `bun run release` — patch version bump, tag, and push; the `v*` tag
440
- triggers `.github/workflows/release.yml`, which verifies (typecheck, test,
441
- build + publint) and publishes.
442
- - `bun run release:minor` — same, minor bump.
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](./ROADMAP.md) — milestones from 0.1.0 through 1.0.0.
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](./src/docs/command-context.md).
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),p(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],m=a[3],h=f.get(d);if(!h||m.length<36||m.length>98340||m.length%2||!/^[0-9a-f]+$/.test(m))throw new i;let g=t(h,p(o,d,n)).decrypt(r(m));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 f(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 p(t,n,r){if(f(r),r.clientFilters===void 0||r.serverFilters===void 0)throw new i;let{table:a,order:s,principal:l,policyVersion:u,engineVersion:d,registryVersion:p}=r;for(let e of[a,l,u,d,p])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:p},o))}export{a as CursorConfigurationError,i as CursorError,d as createCursorCodec};
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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"}
@@ -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, RelationTableHelpers, RelationTables } from "./relations.mjs";
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 QueryEngineConfig<Schema extends SchemaDefinition<GenericSchema, boolean>, Tables extends RelationTables, D extends RelationDeclarations> {
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
- cursor: CursorCodecConfig;
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
- /** Central facade. Endpoint chains carry declarative constraints and explicit relationships. */
35
- export declare function createQueryEngine<Schema extends SchemaDefinition<GenericSchema, boolean>, const Tables extends RelationTables, const D extends RelationDeclarations>(config: QueryEngineConfig<Schema, Tables, D>): ((ctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>) => { [T in keyof Tables]: TableQuery<Tables, D, T>; }) & {
36
- tables: { [T in keyof Tables]: RelationTableHelpers<Tables, D, T>; };
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 r,relationshipExecutor as i}from"./querying/relationships.mjs";import{executeQuery as a}from"./querying/execution.mjs";function o(o){let{schema:s,relations:c}=o,l=t(s,c),u=n(o.cursor),d=o.maxPageSize??100;if(!Number.isSafeInteger(d)||d<=0||d>100)throw Error(`Invalid central page size`);let f=o.engineVersion??`scalar-1`,p=o.policyVersion??`1`,m,h=e({tables:Object.keys(c.tables),policyVersion:p,budgets:o.budgets,trustedAdapter:o.trustedAdapter});function g(e){l.assertUnchanged();let t=h(e);function n(e,o=[],h=[],g={},_){return Object.freeze({when:(t,r)=>n(e,[...o,{value:t,index:r}],h,g,_),where:t=>n(e,o,[...h,t],g,_),with:t=>n(e,o,h,t,_),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=l.metadata.declarations[a]?.[e];if(!t)throw Error(`INVALID_RELATION`);a=t.target}return n(a,o,h,{},{table:e,parent:t,path:i})},async paginate(n){l.assertUnchanged();let v=l.page(e,g);r(g,_?.path.length??0);let y=l.plan(e,n,_?[]:o,h);if(y.args.numItems>d)throw Error(`Page size exceeds central ceiling`);y.args.maximumRowsRead=Math.min(y.args.maximumRowsRead??t.budgets.rawCandidates,t.budgets.rawCandidates),y.args.maximumBytesRead=Math.min(y.args.maximumBytesRead??t.budgets.documentBytes,t.budgets.documentBytes),m??=Array.from(new Uint8Array(await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(c.identity))),e=>e.toString(16).padStart(2,`0`)).join(``),l.assertUnchanged();let b={table:_?.table??e,index:y.index?.name??`by_creation_time`,order:y.args.order??`asc`,clientFilters:y.clientIdentity,serverFilters:{predicates:y.serverIdentity,selection:g,scope:{...t.scope}},relationPath:[],principal:JSON.stringify(t.scope),tenant:t.scope.tenant,policyVersion:p,engineVersion:f,registryVersion:m},x=i(s,t,l,u,b);if(_)return x.continuation(_.table,_.parent,_.path,g,y.args,o,h);let S=await a(s,t,e,y,l,u,b,async(t,n)=>!n||!await x.matches(e,t,y.residual)?null:x.output(e,t,g,y.args));return l.assertUnchanged(),v.parse(S)}})}return Object.freeze(Object.fromEntries(Object.keys(c.tables).map(e=>[e,n(e)])))}return Object.assign(g,{tables:c.tables})}export{o as createQueryEngine};
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
@@ -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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"}
@@ -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