cvx-kit 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -2
- package/dist/errors.d.mts +3 -2
- package/dist/errors.mjs +1 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/filters.d.mts +4 -0
- package/dist/filters.mjs +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/modules/contracts/errors.d.mts +218 -0
- package/dist/modules/contracts/errors.mjs +1 -1
- package/dist/modules/contracts/errors.mjs.map +1 -1
- package/dist/modules/effect/crud.d.mts +28 -0
- package/dist/querying/cursors.d.mts +30 -4
- package/dist/querying/cursors.mjs +1 -1
- package/dist/querying/cursors.mjs.map +1 -1
- package/dist/querying/execution.d.mts +14 -3
- package/dist/querying/execution.mjs +1 -1
- package/dist/querying/execution.mjs.map +1 -1
- package/dist/querying/failures.d.mts +13 -0
- package/dist/querying/failures.mjs +2 -0
- package/dist/querying/failures.mjs.map +1 -0
- package/dist/querying/filters.d.mts +43 -44
- package/dist/querying/filters.mjs +1 -1
- package/dist/querying/filters.mjs.map +1 -1
- package/dist/querying/planner.d.mts +13 -16
- package/dist/querying/planner.mjs +1 -1
- package/dist/querying/planner.mjs.map +1 -1
- package/dist/querying/relationships.d.mts +24 -7
- package/dist/querying/relationships.mjs +1 -1
- package/dist/querying/relationships.mjs.map +1 -1
- package/dist/querying/schema-snapshot.d.mts +29 -0
- package/dist/querying/schema-snapshot.mjs +2 -0
- package/dist/querying/schema-snapshot.mjs.map +1 -0
- package/dist/querying/search.d.mts +69 -0
- package/dist/querying/search.mjs +2 -0
- package/dist/querying/search.mjs.map +1 -0
- package/dist/querying/security.d.mts +19 -2
- package/dist/querying/security.mjs +1 -1
- package/dist/querying/security.mjs.map +1 -1
- package/dist/querying/shared/ast.d.mts +28 -0
- package/dist/querying/shared/ast.mjs +2 -0
- package/dist/querying/shared/ast.mjs.map +1 -0
- package/dist/querying/shared/contract.d.mts +104 -0
- package/dist/querying/shared/contract.mjs +2 -0
- package/dist/querying/shared/contract.mjs.map +1 -0
- package/dist/querying/shared/evaluate.d.mts +7 -0
- package/dist/querying/shared/evaluate.mjs +2 -0
- package/dist/querying/shared/evaluate.mjs.map +1 -0
- package/dist/querying/shared/filters.d.mts +31 -0
- package/dist/querying/shared/filters.mjs +2 -0
- package/dist/querying/shared/filters.mjs.map +1 -0
- package/dist/querying.d.mts +47 -16
- package/dist/querying.mjs +1 -1
- package/dist/querying.mjs.map +1 -1
- package/dist/react/failures.d.mts +23 -0
- package/dist/react/failures.mjs +2 -0
- package/dist/react/failures.mjs.map +1 -0
- package/dist/react/optimistic.d.mts +47 -0
- package/dist/react/optimistic.mjs +2 -0
- package/dist/react/optimistic.mjs.map +1 -0
- package/dist/react/pagination-state.d.mts +40 -0
- package/dist/react/pagination-state.mjs +2 -0
- package/dist/react/pagination-state.mjs.map +1 -0
- package/dist/react/use-command.d.mts +23 -0
- package/dist/react/use-command.mjs +2 -0
- package/dist/react/use-command.mjs.map +1 -0
- package/dist/react/use-paginated-list.d.mts +32 -0
- package/dist/react/use-paginated-list.mjs +2 -0
- package/dist/react/use-paginated-list.mjs.map +1 -0
- package/dist/react.d.mts +6 -0
- package/dist/react.mjs +1 -0
- package/dist/relations-public.d.mts +2 -2
- package/dist/relations.d.mts +65 -22
- package/dist/relations.mjs +1 -1
- package/dist/relations.mjs.map +1 -1
- package/dist/zod-convex.mjs +1 -1
- package/dist/zod-convex.mjs.map +1 -1
- package/dist/zod-table.d.mts +15 -1
- package/dist/zod-table.mjs +1 -1
- package/dist/zod-table.mjs.map +1 -1
- package/package.json +21 -4
package/dist/querying.d.mts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { ManagedBudgets, ManagedQueryContext, ManagedSecurityConfig } from "./querying/security.mjs";
|
|
2
1
|
import { IndexCondition, NativePagination } from "./querying/filters.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { EnginePaginationResult } from "./zod-table.mjs";
|
|
3
|
+
import { ExactGroupSelection, FieldGroupRequest, RelationDeclarations, RelationFilterSelection, RelationFilters, RelationGroupSelection, RelationQueryRow, RelationRegistry, RelationSelection, RelationTables } from "./relations.mjs";
|
|
4
|
+
import { ManagedBudgets, ManagedQueryContext, ManagedScope, ManagedSecurityConfig } from "./querying/security.mjs";
|
|
5
|
+
import { CursorCodecConfig, CursorError, ScopedCursorCodec, ScopedCursorOptions } from "./querying/cursors.mjs";
|
|
6
|
+
import { SearchError, SearchPaginationResult } from "./querying/search.mjs";
|
|
7
|
+
import { queryFailureContract } from "./querying/failures.mjs";
|
|
8
|
+
import { DataModelFromSchemaDefinition, GenericDatabaseReader, GenericSchema, SchemaDefinition } from "convex/server";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { GenericId, Value } from "convex/values";
|
|
7
11
|
//#region src/querying.d.ts
|
|
8
12
|
type FullMask<S> = { [K in keyof S]-?: NonNullable<S[K]> extends true ? true : FullMask<Exclude<NonNullable<S[K]>, true>>; };
|
|
9
13
|
type Paths<D extends RelationDeclarations, T extends string, L extends readonly 1[] = [1, 1, 1, 1]> = L extends readonly [1, ...infer Rest extends 1[]] ? { [K in keyof D[T] & string]: (D[T][K]['cardinality'] extends 'many' ? K : never) | `${K}.${Paths<D, D[T][K]['target'], Rest>}`; }[keyof D[T] & string] : never;
|
|
@@ -15,19 +19,34 @@ type EngineArgs<Tables extends RelationTables, D extends RelationDeclarations, T
|
|
|
15
19
|
order?: 'asc' | 'desc';
|
|
16
20
|
};
|
|
17
21
|
/** Typed declarative chain returned by the engine; named for consumer declaration emission. */
|
|
18
|
-
export interface TableQuery<Tables extends RelationTables, D extends RelationDeclarations, T extends keyof Tables, S = {}, L extends readonly 1[] = [1, 1, 1, 1], Scope extends string = never> {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
export interface TableQuery<Tables extends RelationTables, D extends RelationDeclarations, T extends keyof Tables, S = {}, L extends readonly 1[] = [1, 1, 1, 1], Scope extends string = never, Schema extends SchemaDefinition<GenericSchema, boolean> = SchemaDefinition<GenericSchema, boolean>, G = {}, Root extends boolean = true> {
|
|
23
|
+
search: Root extends true ? (index: Extract<keyof DataModelFromSchemaDefinition<Schema>[Extract<T, string>]['searchIndexes'], string>, text: string) => SearchQuery<Tables, D, T, S, L, Scope, Schema, G> : never;
|
|
24
|
+
include<const Groups extends FieldGroupRequest<Tables[T]>>(groups: Groups & Record<Exclude<keyof Groups, keyof FieldGroupRequest<Tables[T]>>, never>): TableQuery<Tables, D, T, S, L, Scope, Schema, Omit<G, 'fieldGroups'> & {
|
|
25
|
+
fieldGroups: Groups;
|
|
26
|
+
}, Root>;
|
|
27
|
+
orderBy(index: Exclude<Extract<keyof DataModelFromSchemaDefinition<Schema>[Extract<T, string>]['indexes'], string>, 'by_id' | 'by_creation_time'>): TableQuery<Tables, D, T, S, L, Scope, Schema, G, Root>;
|
|
28
|
+
when(value: IndexCondition['value'], index: string): TableQuery<Tables, D, T, S, L, Scope, Schema, G, Root>;
|
|
29
|
+
where(spec: NonNullable<EngineArgs<Tables, D, T>['filters']>): TableQuery<Tables, D, T, S, L, Scope, Schema, G, Root>;
|
|
30
|
+
with<const Selection extends RelationSelection<Tables, D, T, L>, const Groups extends RelationGroupSelection<Tables, D, T, L> = {}>(selection: Selection & { [K in keyof Selection]: K extends keyof RelationSelection<Tables, D, T, L> ? Selection[K] : never; }, groups?: ExactGroupSelection<Tables, D, T, Groups>): TableQuery<Tables, D, T, Selection, L, Scope, Schema, Omit<G, keyof Groups> & Groups, Root>;
|
|
31
|
+
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>, PathScope<D, Extract<T, string>, P>, Schema, {}, false>, 'relation'>;
|
|
32
|
+
paginate(args: EngineArgs<Tables, D, T>): Promise<EnginePaginationResult<RelationQueryRow<Tables, D, T, S, Scope, G>>>;
|
|
33
|
+
}
|
|
34
|
+
export interface SearchQuery<Tables extends RelationTables, D extends RelationDeclarations, T extends keyof Tables, S = {}, L extends readonly 1[] = [1, 1, 1, 1], Scope extends string = never, Schema extends SchemaDefinition<GenericSchema, boolean> = SchemaDefinition<GenericSchema, boolean>, G = {}> {
|
|
35
|
+
include<const Groups extends FieldGroupRequest<Tables[T]>>(groups: Groups & Record<Exclude<keyof Groups, keyof FieldGroupRequest<Tables[T]>>, never>): SearchQuery<Tables, D, T, S, L, Scope, Schema, Omit<G, 'fieldGroups'> & {
|
|
36
|
+
fieldGroups: Groups;
|
|
37
|
+
}>;
|
|
38
|
+
where(spec: NonNullable<EngineArgs<Tables, D, T>['filters']>): SearchQuery<Tables, D, T, S, L, Scope, Schema, G>;
|
|
39
|
+
with<const Selection extends RelationSelection<Tables, D, T, L>, const Groups extends RelationGroupSelection<Tables, D, T, L> = {}>(selection: Selection & { [K in keyof Selection]: K extends keyof RelationSelection<Tables, D, T, L> ? Selection[K] : never; }, groups?: ExactGroupSelection<Tables, D, T, Groups>): SearchQuery<Tables, D, T, Selection, L, Scope, Schema, Omit<G, keyof Groups> & Groups>;
|
|
40
|
+
paginate(args: Omit<EngineArgs<Tables, D, T>, 'order' | 'endCursor' | 'maximumRowsRead' | 'maximumBytesRead' | 'onEmptyBudget'> & {
|
|
41
|
+
scanLimit?: number;
|
|
42
|
+
}): Promise<SearchPaginationResult<RelationQueryRow<Tables, D, T, S, Scope, G>>>;
|
|
24
43
|
}
|
|
25
44
|
export interface QueryEngineDeclaration<Schema extends SchemaDefinition<GenericSchema, boolean>, Tables extends RelationTables, D extends RelationDeclarations> {
|
|
26
45
|
schema: Schema;
|
|
27
46
|
relations: RelationRegistry<Tables, D>;
|
|
28
47
|
}
|
|
29
48
|
/** Advanced server configuration, separate from the normal schema/relations declaration. */
|
|
30
|
-
export interface QueryEngineOptions<Schema extends SchemaDefinition<GenericSchema, boolean
|
|
49
|
+
export interface QueryEngineOptions<Schema extends SchemaDefinition<GenericSchema, boolean>, Tables extends RelationTables = RelationTables> {
|
|
31
50
|
/** Explicit codecs validate at creation. Otherwise environment keys load at first paginate. */
|
|
32
51
|
cursor?: CursorCodecConfig;
|
|
33
52
|
budgets?: Partial<ManagedBudgets>;
|
|
@@ -35,20 +54,32 @@ export interface QueryEngineOptions<Schema extends SchemaDefinition<GenericSchem
|
|
|
35
54
|
policyVersion?: string;
|
|
36
55
|
engineVersion?: string;
|
|
37
56
|
trustedAdapter?: ManagedSecurityConfig<DataModelFromSchemaDefinition<Schema>>['trustedAdapter'];
|
|
57
|
+
fieldGroups?: { [T in keyof Tables]?: { [G in keyof NonNullable<Tables[T]['fieldGroups']>]?: (ctx: FieldGroupPolicyContext<Schema>) => boolean | Promise<boolean>; }; };
|
|
58
|
+
}
|
|
59
|
+
export interface FieldGroupPolicyContext<Schema extends SchemaDefinition<GenericSchema, boolean>> {
|
|
60
|
+
readonly db: GenericDatabaseReader<DataModelFromSchemaDefinition<Schema>>;
|
|
61
|
+
readonly scope: ManagedScope;
|
|
62
|
+
}
|
|
63
|
+
export declare class FieldGroupError extends Error {
|
|
64
|
+
readonly code = "FIELD_GROUP_DENIED";
|
|
65
|
+
constructor();
|
|
38
66
|
}
|
|
39
67
|
/** @deprecated Use QueryEngineDeclaration and optional QueryEngineOptions separately. */
|
|
40
|
-
export interface QueryEngineConfig<Schema extends SchemaDefinition<GenericSchema, boolean>, Tables extends RelationTables, D extends RelationDeclarations> extends QueryEngineDeclaration<Schema, Tables, D>, QueryEngineOptions<Schema> {
|
|
68
|
+
export interface QueryEngineConfig<Schema extends SchemaDefinition<GenericSchema, boolean>, Tables extends RelationTables, D extends RelationDeclarations> extends QueryEngineDeclaration<Schema, Tables, D>, QueryEngineOptions<Schema, Tables> {
|
|
41
69
|
cursor: CursorCodecConfig;
|
|
42
70
|
}
|
|
43
71
|
type QueryEngine<Schema extends SchemaDefinition<GenericSchema, boolean>, Tables extends RelationTables, D extends RelationDeclarations> = {
|
|
44
|
-
(ctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>): { [T in keyof Tables]: TableQuery<Tables, D, T>; };
|
|
72
|
+
(ctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>): { [T in keyof Tables]: TableQuery<Tables, D, T, {}, [1, 1, 1, 1], never, Schema>; };
|
|
73
|
+
/** Opens prove integrity only; native consumers must enforce current row authorization. */
|
|
74
|
+
cursorCodec<S extends z.ZodType<Value>>(ctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>, options: ScopedCursorOptions<S>): ScopedCursorCodec<S>;
|
|
45
75
|
tables: RelationRegistry<Tables, D>['tables'];
|
|
46
76
|
};
|
|
47
77
|
/** Central facade. Endpoint chains carry declarative constraints and explicit relationships. */
|
|
48
|
-
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>;
|
|
78
|
+
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, Tables>): QueryEngine<Schema, Tables, D>;
|
|
49
79
|
/** @deprecated Pass advanced fields in the optional second argument. */
|
|
50
|
-
export declare function createQueryEngine<Schema extends SchemaDefinition<GenericSchema, boolean>, const Tables extends RelationTables, const D extends RelationDeclarations>(config: QueryEngineDeclaration<Schema, Tables, D> & QueryEngineOptions<Schema> & {
|
|
80
|
+
export declare function createQueryEngine<Schema extends SchemaDefinition<GenericSchema, boolean>, const Tables extends RelationTables, const D extends RelationDeclarations>(config: QueryEngineDeclaration<Schema, Tables, D> & QueryEngineOptions<Schema, Tables> & {
|
|
51
81
|
cursor: CursorCodecConfig;
|
|
52
82
|
}): QueryEngine<Schema, Tables, D>;
|
|
53
83
|
//#endregion
|
|
84
|
+
export { CursorError, type ScopedCursorCodec, type ScopedCursorOptions, SearchError, type SearchPaginationResult, queryFailureContract };
|
|
54
85
|
//# sourceMappingURL=querying.d.mts.map
|
package/dist/querying.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createManagedSecurity as
|
|
1
|
+
import{ManagedReadError as e,createManagedSecurity as t}from"./querying/security.mjs";import{createPlanner as n}from"./querying/planner.mjs";import{CursorError as r,createCursorCodec as i,createEnvironmentCursorCodec as a,createScopedCursorCodec as o}from"./querying/cursors.mjs";import{SearchError as s,executeSearch as c,planSearch as l}from"./querying/search.mjs";import{assertRelationshipDepth as u,relationshipExecutor as d}from"./querying/relationships.mjs";import{executeQuery as f}from"./querying/execution.mjs";import{queryFailureContract as p}from"./querying/failures.mjs";import{z as m}from"zod";const h={fieldGroups:{},relations:{}};var g=class extends Error{code=`FIELD_GROUP_DENIED`;constructor(){super(`FIELD_GROUP_DENIED`),this.name=`FieldGroupError`}};function _(r,p){let{relations:_}=r,y={...r,...p},b=n(r.schema,_),x=b.streamSchema,S=/* @__PURE__ */ new Map;for(let[e,t]of Object.entries(y.fieldGroups??{})){if(!Object.hasOwn(b.metadata.tables,e))throw Error(`Unknown field group policy table`);for(let[n,r]of Object.entries(t??{})){if(!Object.hasOwn(b.metadata.tables[e].fieldGroups,n)||!(r instanceof Function))throw Error(`Unknown field group policy`);S.set(JSON.stringify([e,n]),r)}}let C=y.cursor===void 0?void 0:i(y.cursor),w=y.maxPageSize??100;if(!Number.isSafeInteger(w)||w<=0||w>100)throw Error(`Invalid central page size`);let T=y.engineVersion??`scalar-1`,E=y.policyVersion??`1`,D,O=t({tables:Object.keys(_.tables),policyVersion:E,budgets:y.budgets,trustedAdapter:y.trustedAdapter});function k(t){let n=O(t),r=/* @__PURE__ */ new Map;async function i(t,a){let o={};for(let[i,s]of Object.entries(a.fieldGroups??{})){let a=JSON.stringify([t,i]),c=r.get(a);if(c||(c=Promise.resolve().then(async()=>{try{return await S.get(a)?.(Object.freeze({db:n.db,scope:n.scope}))===!0}catch(t){throw t instanceof e?t:new g}}),r.set(a,c)),o[i]={mode:s,allowed:await c},s===`required`&&!o[i].allowed)throw new g}let s={};for(let[e,n]of Object.entries(a.relations??{}))s[e]=await i(b.metadata.declarations[t][e].target,n);return{fieldGroups:o,relations:s}}function o(e,t=[],r=[],p={},y,S,O={},k){return Object.freeze({include:n=>o(e,t,r,p,y,S,b.groups(e,{...O,fieldGroups:n}),k),orderBy:n=>{if(k)throw new s(`SEARCH_ORDER_UNSUPPORTED`);if(!m.string().safeParse(n).success)throw Error(`Unknown ordering index`);return o(e,t,r,p,y,n,O,k)},when:(n,i)=>{if(k&&n!=null)throw new s(`SEARCH_ORDER_UNSUPPORTED`);return o(e,[...t,{value:n,index:i}],r,p,y,S,O,k)},where:n=>o(e,t,[...r,n],p,y,S,O,k),with:(n,i={})=>o(e,t,r,n,y,S,b.groups(e,{...O,...i}),k),search:(n,i)=>{if(y)throw new s(`SEARCH_RELATION_UNSUPPORTED`);if(S!==void 0||t.some(({value:e})=>e!=null))throw new s(`SEARCH_ORDER_UNSUPPORTED`);if(k)throw new s(`SEARCH_OPTIONS_UNSUPPORTED`);return o(e,t,r,p,y,S,O,{index:n,text:i})},relation:(n,i)=>{if(k)throw new s(`SEARCH_RELATION_UNSUPPORTED`);let a=i.split(`.`);if(!a.length||a.length>4)throw Error(`INVALID_RELATION`);let c=e;for(let e of a){let t=b.metadata.declarations[c]?.[e];if(!t)throw Error(`INVALID_RELATION`);c=t.target}return o(c,t,r,{},{table:e,parent:n,path:a},S)},async paginate(o){let s=C??=a();b.select(e,p),u(p,y?.path.length??0);let m=k?l(b,e,k.index,k.text,o,r):void 0,A=m?.plan??b.plan(e,o,y?[]:t,r,S,y?`relation`:`root`);if(!m&&A.args.numItems>w)throw Error(`Page size exceeds central ceiling`);let j=await i(e,b.groups(e,O)),M={at(e){return e.slice(y?.path.length??0).reduce((e,t)=>e.relations[t]??h,j)},allowed(e){return Object.entries(this.at(e).fieldGroups).filter(([,e])=>e.allowed).map(([e])=>e)},checkOrder(e,t,n){for(let r of t)N(e,r,n?this.at(n):h)}};function N(e,t,n){for(let[r,i]of Object.entries(b.metadata.tables[e].fieldGroups))if(i.includes(t.split(`.`)[0])&&n.fieldGroups[r]?.allowed!==!0)throw new g}function P(e,t,n){if(t.kind===`scalar`)N(e,t.field,n);else if(t.kind===`variant`)N(e,t.discriminator,n),P(e,t.child,n);else if(t.kind===`not`)P(e,t.child,n);else if(t.kind===`and`||t.kind===`or`)for(let r of t.children)P(e,r,n);else t.kind===`relation`&&t.predicate&&P(b.metadata.declarations[e][t.name].target,t.predicate,n.relations[t.name]??h)}if(A.residual.kind!==`and`)throw Error(`Invalid compiled filter root`);P(e,A.residual.children[0],j),m&&N(e,m.metadata.searchField,j);let F=S??t.find(e=>e.value!=null)?.index;if(!m&&F!==void 0)for(let t of b.metadata.tables[e].indexes.find(e=>e.name===F)?.fields??[])N(e,t,j);else if(!m&&!y&&A.index)for(let t of A.index.fields.slice(A.prefix.length))N(e,t,j);A.args.maximumRowsRead=Math.min(A.args.maximumRowsRead??n.budgets.rawCandidates,n.budgets.rawCandidates),A.args.maximumBytesRead=Math.min(A.args.maximumBytesRead??n.budgets.documentBytes,n.budgets.documentBytes),D??=Array.from(new Uint8Array(await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(_.identity))),e=>e.toString(16).padStart(2,`0`)).join(``);let I={};v(j)&&(I.fieldGroups=j);let L={predicates:A.serverIdentity,selection:p,...I,scope:{...n.scope}};m&&(L.search={mode:`search`,text:m.text,index:{...m.metadata,filterFields:[...m.metadata.filterFields]},equalities:m.equalities,scanLimit:m.scanLimit??null});let R={table:y?.table??e,index:m?.metadata.name??A.index?.name??`by_creation_time`,order:A.args.order??`asc`,clientFilters:A.clientIdentity,serverFilters:L,relationPath:[],principal:JSON.stringify(n.scope),tenant:n.scope.tenant,policyVersion:E,engineVersion:T,registryVersion:D},z=n,B=d(x,z,b,s,R,M);return B.preflight(e,p,A.args,y?.path??[]),m?await c(z,e,m,s,R,w,async t=>{let r=t;return!await n.authorize(e,t)||!await B.matches(e,r,A.residual)?null:B.output(e,r,p,A.args)}):y?await B.continuation(y.table,y.parent,y.path,p,A.args,t,r,S):await f(x,n,e,A,b,s,R,async(t,n)=>!n||!await B.matches(e,t,A.residual)?null:B.output(e,t,p,A.args))}})}return Object.freeze(Object.fromEntries(Object.keys(_.tables).map(e=>[e,o(e)])))}return Object.assign(k,{tables:_.tables,cursorCodec(e,t){let n=O.cursorScope(e);return C??=a(),o(C.custom,n,t)}})}function v(e){return Object.keys(e.fieldGroups).length>0||Object.values(e.relations).some(v)}export{r as CursorError,g as FieldGroupError,s as SearchError,_ as createQueryEngine,p as queryFailureContract};
|
|
2
2
|
//# sourceMappingURL=querying.mjs.map
|
package/dist/querying.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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\tRelationQueryRow,\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\n\t\t? true\n\t\t: FullMask<Exclude<NonNullable<S[K]>, true>>\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 PathScope<\n\tD extends RelationDeclarations,\n\tT extends string,\n\tP extends string,\n> = P extends `${infer Head}.${infer Rest}`\n\t? PathScope<D, D[T][Head]['target'], Rest>\n\t: Extract<NonNullable<D[T][P]['targetGuard']>['tag'], string>\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\tScope extends string = never,\n> {\n\twhen(value: IndexCondition['value'], index: string): TableQuery<Tables, D, T, S, L, Scope>\n\twhere(\n\t\tspec: NonNullable<EngineArgs<Tables, D, T>['filters']>,\n\t): TableQuery<Tables, D, T, S, L, Scope>\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, Scope>\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\tPathScope<D, Extract<T, string>, P>\n\t\t>,\n\t\t'relation'\n\t>\n\tpaginate(\n\t\targs: EngineArgs<Tables, D, T>,\n\t): Promise<PaginationResult<RelationQueryRow<Tables, D, T, S, Scope>>>\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
|
+
{"version":3,"file":"querying.mjs","names":["executorRequest","emptyGroups","FieldGroupError","createQueryEngine","config","settings","options","planner","createPlanner","relations","schema","policies","table","definitions","name","policy","cursorCodec","createCursorCodec","maxPageSize","engineVersion","policyVersion","registryDigest","security","createManagedSecurity","querying","request","ctx","decisions","resolveGroups","fieldGroups","mode","groups","key","decision","error","ManagedReadError","nested","selection","tableQuery","conditions","constraints","route","ordering","search","SearchError","z","index","value","spec","selected","requested","path","declaredPath","target","edge","codec","createEnvironmentCursorCodec","assertRelationshipDepth","searchPlan","planSearch","input","plan","resolved","groupAccess","node","field","fields","checkField","checkClient","ast","child","sortIndex","condition","byte","groupBinding","hasGroups","serverFilters","binding","executionRequest","executor","relationshipExecutor","executeSearch","document","row","executeQuery","permitted","scope","createScopedCursorCodec","CursorError","queryFailureContract"],"sources":["../src/querying.ts"],"sourcesContent":["import type { GenericSchema, SchemaDefinition, DataModelFromSchemaDefinition } from 'convex/server'\nimport type { GenericId } from 'convex/values'\nimport type { EnginePaginationResult } from './zod-table'\nimport { z } from 'zod'\nimport type {\n\tRelationTables,\n\tRelationDeclarations,\n\tRelationRegistry,\n\tRelationQueryRow,\n\tRelationSelection,\n\tRelationFilters,\n\tRelationFilterSelection,\n\tFieldGroupRequest,\n\tRelationGroupSelection,\n\tGroupSelection,\n\tExactGroupSelection,\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\ttype GroupAccess,\n\ttype ResolvedGroups,\n} from './querying/relationships'\nimport {\n\tcreateCursorCodec,\n\tcreateScopedCursorCodec,\n\ttype ScopedCursorOptions,\n\ttype ScopedCursorCodec,\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\ttype ManagedScope,\n\tManagedReadError,\n} from './querying/security'\nimport type { FilterAst, IndexCondition, NativePagination } from './querying/filters'\nimport type { GenericDatabaseReader } from 'convex/server'\nimport type { Value } from 'convex/values'\n\nimport { planSearch, executeSearch, SearchError } from './querying/search'\nexport { SearchError } from './querying/search'\nexport type { SearchPaginationResult } from './querying/search'\nexport { queryFailureContract } from './querying/failures'\nexport { CursorError } from './querying/cursors'\nexport type { ScopedCursorOptions, ScopedCursorCodec } from './querying/cursors'\n\nconst emptyGroups: ResolvedGroups = { fieldGroups: {}, relations: {} }\n\ntype FullMask<S> = {\n\t[K in keyof S]-?: NonNullable<S[K]> extends true\n\t\t? true\n\t\t: FullMask<Exclude<NonNullable<S[K]>, true>>\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 PathScope<\n\tD extends RelationDeclarations,\n\tT extends string,\n\tP extends string,\n> = P extends `${infer Head}.${infer Rest}`\n\t? PathScope<D, D[T][Head]['target'], Rest>\n\t: Extract<NonNullable<D[T][P]['targetGuard']>['tag'], string>\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\tScope extends string = never,\n\tSchema extends SchemaDefinition<GenericSchema, boolean> = SchemaDefinition<\n\t\tGenericSchema,\n\t\tboolean\n\t>,\n\tG = {},\n\tRoot extends boolean = true,\n> {\n\tsearch: Root extends true\n\t\t? (\n\t\t\t\tindex: Extract<\n\t\t\t\t\tkeyof DataModelFromSchemaDefinition<Schema>[Extract<T, string>]['searchIndexes'],\n\t\t\t\t\tstring\n\t\t\t\t>,\n\t\t\t\ttext: string,\n\t\t\t) => SearchQuery<Tables, D, T, S, L, Scope, Schema, G>\n\t\t: never\n\tinclude<const Groups extends FieldGroupRequest<Tables[T]>>(\n\t\tgroups: Groups & Record<Exclude<keyof Groups, keyof FieldGroupRequest<Tables[T]>>, never>,\n\t): TableQuery<\n\t\tTables,\n\t\tD,\n\t\tT,\n\t\tS,\n\t\tL,\n\t\tScope,\n\t\tSchema,\n\t\tOmit<G, 'fieldGroups'> & { fieldGroups: Groups },\n\t\tRoot\n\t>\n\torderBy(\n\t\tindex: Exclude<\n\t\t\tExtract<keyof DataModelFromSchemaDefinition<Schema>[Extract<T, string>]['indexes'], string>,\n\t\t\t'by_id' | 'by_creation_time'\n\t\t>,\n\t): TableQuery<Tables, D, T, S, L, Scope, Schema, G, Root>\n\twhen(\n\t\tvalue: IndexCondition['value'],\n\t\tindex: string,\n\t): TableQuery<Tables, D, T, S, L, Scope, Schema, G, Root>\n\twhere(\n\t\tspec: NonNullable<EngineArgs<Tables, D, T>['filters']>,\n\t): TableQuery<Tables, D, T, S, L, Scope, Schema, G, Root>\n\twith<\n\t\tconst Selection extends RelationSelection<Tables, D, T, L>,\n\t\tconst Groups extends RelationGroupSelection<Tables, D, T, L> = {},\n\t>(\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\tgroups?: ExactGroupSelection<Tables, D, T, Groups>,\n\t): TableQuery<Tables, D, T, Selection, L, Scope, Schema, Omit<G, keyof Groups> & Groups, Root>\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\tPathScope<D, Extract<T, string>, P>,\n\t\t\tSchema,\n\t\t\t{},\n\t\t\tfalse\n\t\t>,\n\t\t'relation'\n\t>\n\tpaginate(\n\t\targs: EngineArgs<Tables, D, T>,\n\t): Promise<EnginePaginationResult<RelationQueryRow<Tables, D, T, S, Scope, G>>>\n}\n\nexport interface SearchQuery<\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\tScope extends string = never,\n\tSchema extends SchemaDefinition<GenericSchema, boolean> = SchemaDefinition<\n\t\tGenericSchema,\n\t\tboolean\n\t>,\n\tG = {},\n> {\n\tinclude<const Groups extends FieldGroupRequest<Tables[T]>>(\n\t\tgroups: Groups & Record<Exclude<keyof Groups, keyof FieldGroupRequest<Tables[T]>>, never>,\n\t): SearchQuery<\n\t\tTables,\n\t\tD,\n\t\tT,\n\t\tS,\n\t\tL,\n\t\tScope,\n\t\tSchema,\n\t\tOmit<G, 'fieldGroups'> & { fieldGroups: Groups }\n\t>\n\twhere(\n\t\tspec: NonNullable<EngineArgs<Tables, D, T>['filters']>,\n\t): SearchQuery<Tables, D, T, S, L, Scope, Schema, G>\n\twith<\n\t\tconst Selection extends RelationSelection<Tables, D, T, L>,\n\t\tconst Groups extends RelationGroupSelection<Tables, D, T, L> = {},\n\t>(\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\tgroups?: ExactGroupSelection<Tables, D, T, Groups>,\n\t): SearchQuery<Tables, D, T, Selection, L, Scope, Schema, Omit<G, keyof Groups> & Groups>\n\tpaginate(\n\t\targs: Omit<\n\t\t\tEngineArgs<Tables, D, T>,\n\t\t\t'order' | 'endCursor' | 'maximumRowsRead' | 'maximumBytesRead' | 'onEmptyBudget'\n\t\t> & { scanLimit?: number },\n\t): Promise<\n\t\timport('./querying/search').SearchPaginationResult<RelationQueryRow<Tables, D, T, S, Scope, G>>\n\t>\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<\n\tSchema extends SchemaDefinition<GenericSchema, boolean>,\n\tTables extends RelationTables = RelationTables,\n> {\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\tfieldGroups?: {\n\t\t[T in keyof Tables]?: {\n\t\t\t[G in keyof NonNullable<Tables[T]['fieldGroups']>]?: (\n\t\t\t\tctx: FieldGroupPolicyContext<Schema>,\n\t\t\t) => boolean | Promise<boolean>\n\t\t}\n\t}\n}\nexport interface FieldGroupPolicyContext<Schema extends SchemaDefinition<GenericSchema, boolean>> {\n\treadonly db: GenericDatabaseReader<DataModelFromSchemaDefinition<Schema>>\n\treadonly scope: ManagedScope\n}\nexport class FieldGroupError extends Error {\n\treadonly code = 'FIELD_GROUP_DENIED'\n\tconstructor() {\n\t\tsuper('FIELD_GROUP_DENIED')\n\t\tthis.name = 'FieldGroupError'\n\t}\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, Tables> {\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, {}, [1, 1, 1, 1], never, Schema>\n\t}\n\t/** Opens prove integrity only; native consumers must enforce current row authorization. */\n\tcursorCodec<S extends z.ZodType<Value>>(\n\t\tctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>,\n\t\toptions: ScopedCursorOptions<S>,\n\t): ScopedCursorCodec<S>\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, Tables>,\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, Tables> & { 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, Tables>,\n\toptions?: QueryEngineOptions<Schema, Tables>,\n) {\n\tconst { relations } = config\n\t// Retain released one-argument calls; a second argument overrides legacy fields.\n\tconst settings = { ...config, ...options }\n\tconst planner = createPlanner(config.schema, relations)\n\tconst schema = planner.streamSchema\n\t// Copy callback references once: later option mutation cannot replace engine authority.\n\tconst policies = new Map<\n\t\tstring,\n\t\t(ctx: FieldGroupPolicyContext<Schema>) => boolean | Promise<boolean>\n\t>()\n\tfor (const [table, definitions] of Object.entries(settings.fieldGroups ?? {})) {\n\t\tif (!Object.hasOwn(planner.metadata.tables, table))\n\t\t\tthrow new Error('Unknown field group policy table')\n\t\tfor (const [name, policy] of Object.entries(definitions ?? {})) {\n\t\t\tif (\n\t\t\t\t!Object.hasOwn(planner.metadata.tables[table].fieldGroups, name) ||\n\t\t\t\t!(policy instanceof Function)\n\t\t\t)\n\t\t\t\tthrow new Error('Unknown field group policy')\n\t\t\t// SAFETY: the public mapped options require this policy signature; the function check also rejects malformed JS callers.\n\t\t\tpolicies.set(\n\t\t\t\tJSON.stringify([table, name]),\n\t\t\t\tpolicy as (ctx: FieldGroupPolicyContext<Schema>) => boolean | Promise<boolean>,\n\t\t\t)\n\t\t}\n\t}\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.\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\tconst request = security(ctx)\n\t\tconst decisions = new Map<string, Promise<boolean>>()\n\t\tasync function resolveGroups(table: string, groups: GroupSelection): Promise<ResolvedGroups> {\n\t\t\tconst fieldGroups: ResolvedGroups['fieldGroups'] = {}\n\t\t\tfor (const [name, mode] of Object.entries(groups.fieldGroups ?? {})) {\n\t\t\t\tconst key = JSON.stringify([table, name])\n\t\t\t\tlet decision = decisions.get(key)\n\t\t\t\tif (!decision) {\n\t\t\t\t\tdecision = Promise.resolve().then(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t(await policies.get(key)?.(\n\t\t\t\t\t\t\t\t\tObject.freeze({ db: request.db, scope: request.scope }),\n\t\t\t\t\t\t\t\t)) === true\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tif (error instanceof ManagedReadError) throw error\n\t\t\t\t\t\t\tthrow new FieldGroupError()\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tdecisions.set(key, decision)\n\t\t\t\t}\n\t\t\t\tfieldGroups[name] = { mode, allowed: await decision }\n\t\t\t\tif (mode === 'required' && !fieldGroups[name].allowed) throw new FieldGroupError()\n\t\t\t}\n\t\t\tconst nested: ResolvedGroups['relations'] = {}\n\t\t\tfor (const [name, selection] of Object.entries(groups.relations ?? {}))\n\t\t\t\tnested[name] = await resolveGroups(\n\t\t\t\t\tplanner.metadata.declarations[table][name].target,\n\t\t\t\t\tselection,\n\t\t\t\t)\n\t\t\treturn { fieldGroups, relations: nested }\n\t\t}\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\tordering?: string,\n\t\t\tgroups: GroupSelection = {},\n\t\t\tsearch?: { index: string; text: string },\n\t\t) {\n\t\t\treturn Object.freeze({\n\t\t\t\tinclude: (fieldGroups: GroupSelection['fieldGroups']) =>\n\t\t\t\t\ttableQuery(\n\t\t\t\t\t\ttable,\n\t\t\t\t\t\tconditions,\n\t\t\t\t\t\tconstraints,\n\t\t\t\t\t\tselection,\n\t\t\t\t\t\troute,\n\t\t\t\t\t\tordering,\n\t\t\t\t\t\tplanner.groups(table, { ...groups, fieldGroups }),\n\t\t\t\t\t\tsearch,\n\t\t\t\t\t),\n\t\t\t\torderBy: (index: string) => {\n\t\t\t\t\tif (search) throw new SearchError('SEARCH_ORDER_UNSUPPORTED')\n\t\t\t\t\tif (!z.string().safeParse(index).success) throw new Error('Unknown ordering index')\n\t\t\t\t\treturn tableQuery(table, conditions, constraints, selection, route, index, groups, search)\n\t\t\t\t},\n\t\t\t\twhen: (value: IndexCondition['value'], index: string) => {\n\t\t\t\t\tif (search && value != null) throw new SearchError('SEARCH_ORDER_UNSUPPORTED')\n\t\t\t\t\treturn tableQuery(\n\t\t\t\t\t\ttable,\n\t\t\t\t\t\t[...conditions, { value, index }],\n\t\t\t\t\t\tconstraints,\n\t\t\t\t\t\tselection,\n\t\t\t\t\t\troute,\n\t\t\t\t\t\tordering,\n\t\t\t\t\t\tgroups,\n\t\t\t\t\t\tsearch,\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t\twhere: (spec: StoredConstraint) =>\n\t\t\t\t\ttableQuery(\n\t\t\t\t\t\ttable,\n\t\t\t\t\t\tconditions,\n\t\t\t\t\t\t[...constraints, spec],\n\t\t\t\t\t\tselection,\n\t\t\t\t\t\troute,\n\t\t\t\t\t\tordering,\n\t\t\t\t\t\tgroups,\n\t\t\t\t\t\tsearch,\n\t\t\t\t\t),\n\t\t\t\twith: (selected: OutputSelection, requested: GroupSelection = {}) =>\n\t\t\t\t\ttableQuery(\n\t\t\t\t\t\ttable,\n\t\t\t\t\t\tconditions,\n\t\t\t\t\t\tconstraints,\n\t\t\t\t\t\tselected,\n\t\t\t\t\t\troute,\n\t\t\t\t\t\tordering,\n\t\t\t\t\t\tplanner.groups(table, { ...groups, ...requested }),\n\t\t\t\t\t\tsearch,\n\t\t\t\t\t),\n\t\t\t\tsearch: (index: string, text: string) => {\n\t\t\t\t\tif (route) throw new SearchError('SEARCH_RELATION_UNSUPPORTED')\n\t\t\t\t\tif (ordering !== undefined || conditions.some(({ value }) => value != null))\n\t\t\t\t\t\tthrow new SearchError('SEARCH_ORDER_UNSUPPORTED')\n\t\t\t\t\tif (search) throw new SearchError('SEARCH_OPTIONS_UNSUPPORTED')\n\t\t\t\t\treturn tableQuery(table, conditions, constraints, selection, route, ordering, groups, {\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\ttext,\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\trelation: (parent: string, declaredPath: string) => {\n\t\t\t\t\tif (search) throw new SearchError('SEARCH_RELATION_UNSUPPORTED')\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 }, ordering)\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.select(table, selection)\n\t\t\t\t\tassertRelationshipDepth(selection, route?.path.length ?? 0)\n\t\t\t\t\tconst searchPlan = search\n\t\t\t\t\t\t? planSearch(\n\t\t\t\t\t\t\t\tplanner,\n\t\t\t\t\t\t\t\ttable,\n\t\t\t\t\t\t\t\tsearch.index,\n\t\t\t\t\t\t\t\tsearch.text,\n\t\t\t\t\t\t\t\tinput,\n\t\t\t\t\t\t\t\tconstraints,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: undefined\n\t\t\t\t\tconst plan =\n\t\t\t\t\t\tsearchPlan?.plan ??\n\t\t\t\t\t\tplanner.plan(\n\t\t\t\t\t\t\ttable,\n\t\t\t\t\t\t\tinput,\n\t\t\t\t\t\t\troute ? [] : conditions,\n\t\t\t\t\t\t\tconstraints,\n\t\t\t\t\t\t\tordering,\n\t\t\t\t\t\t\troute ? 'relation' : 'root',\n\t\t\t\t\t\t)\n\t\t\t\t\tif (!searchPlan && plan.args.numItems > maxPageSize)\n\t\t\t\t\t\tthrow new Error('Page size exceeds central ceiling')\n\t\t\t\t\tconst resolved = await resolveGroups(table, planner.groups(table, groups))\n\t\t\t\t\tconst groupAccess: GroupAccess = {\n\t\t\t\t\t\tat(path) {\n\t\t\t\t\t\t\treturn path\n\t\t\t\t\t\t\t\t.slice(route?.path.length ?? 0)\n\t\t\t\t\t\t\t\t.reduce((node, name) => node.relations[name] ?? emptyGroups, resolved)\n\t\t\t\t\t\t},\n\t\t\t\t\t\tallowed(path) {\n\t\t\t\t\t\t\treturn Object.entries(this.at(path).fieldGroups)\n\t\t\t\t\t\t\t\t.filter(([, decision]) => decision.allowed)\n\t\t\t\t\t\t\t\t.map(([name]) => name)\n\t\t\t\t\t\t},\n\t\t\t\t\t\tcheckOrder(table, fields, path) {\n\t\t\t\t\t\t\tfor (const field of fields)\n\t\t\t\t\t\t\t\tcheckField(table, field, path ? this.at(path) : emptyGroups)\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t\tfunction checkField(table: string, field: string, groups: ResolvedGroups) {\n\t\t\t\t\t\tfor (const [name, fields] of Object.entries(planner.metadata.tables[table].fieldGroups))\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tfields.includes(field.split('.')[0]) &&\n\t\t\t\t\t\t\t\tgroups.fieldGroups[name]?.allowed !== true\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tthrow new FieldGroupError()\n\t\t\t\t\t}\n\t\t\t\t\tfunction checkClient(table: string, ast: FilterAst, groups: ResolvedGroups): void {\n\t\t\t\t\t\tif (ast.kind === 'scalar') checkField(table, ast.field, groups)\n\t\t\t\t\t\telse if (ast.kind === 'variant') {\n\t\t\t\t\t\t\tcheckField(table, ast.discriminator, groups)\n\t\t\t\t\t\t\tcheckClient(table, ast.child, groups)\n\t\t\t\t\t\t} else if (ast.kind === 'not') checkClient(table, ast.child, groups)\n\t\t\t\t\t\telse if (ast.kind === 'and' || ast.kind === 'or')\n\t\t\t\t\t\t\tfor (const child of ast.children) checkClient(table, child, groups)\n\t\t\t\t\t\telse if (ast.kind === 'relation' && ast.predicate)\n\t\t\t\t\t\t\tcheckClient(\n\t\t\t\t\t\t\t\tplanner.metadata.declarations[table][ast.name].target,\n\t\t\t\t\t\t\t\tast.predicate,\n\t\t\t\t\t\t\t\tgroups.relations[ast.name] ?? emptyGroups,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\t// Inspect the whole client tree independently of execution/contradiction short circuits.\n\t\t\t\t\tif (plan.residual.kind !== 'and') throw new Error('Invalid compiled filter root')\n\t\t\t\t\tcheckClient(table, plan.residual.children[0], resolved)\n\t\t\t\t\tif (searchPlan) checkField(table, searchPlan.metadata.searchField, resolved)\n\t\t\t\t\tconst sortIndex =\n\t\t\t\t\t\tordering ?? conditions.find((condition) => condition.value != null)?.index\n\t\t\t\t\tif (!searchPlan && sortIndex !== undefined)\n\t\t\t\t\t\tfor (const field of planner.metadata.tables[table].indexes.find(\n\t\t\t\t\t\t\t(index) => index.name === sortIndex,\n\t\t\t\t\t\t)?.fields ?? [])\n\t\t\t\t\t\t\tcheckField(table, field, resolved)\n\t\t\t\t\telse if (!searchPlan && !route && plan.index)\n\t\t\t\t\t\tfor (const field of plan.index.fields.slice(plan.prefix.length))\n\t\t\t\t\t\t\tcheckField(table, field, resolved)\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\tconst groupBinding: Record<string, Value> = {}\n\t\t\t\t\tif (hasGroups(resolved)) groupBinding.fieldGroups = resolved\n\t\t\t\t\t// SAFETY: selection has passed registry admission before cursor binding.\n\t\t\t\t\tconst serverFilters: CursorBinding['serverFilters'] = {\n\t\t\t\t\t\tpredicates: plan.serverIdentity,\n\t\t\t\t\t\tselection: selection as import('convex/values').Value,\n\t\t\t\t\t\t...groupBinding,\n\t\t\t\t\t\tscope: { ...request.scope },\n\t\t\t\t\t}\n\t\t\t\t\tif (searchPlan) serverFilters.search = {\n\t\t\t\t\t\tmode: 'search', text: searchPlan.text,\n\t\t\t\t\t\tindex: {...searchPlan.metadata, filterFields: [...searchPlan.metadata.filterFields]},\n\t\t\t\t\t\tequalities: searchPlan.equalities, scanLimit: searchPlan.scanLimit ?? null,\n\t\t\t\t\t}\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: searchPlan?.metadata.name ?? 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\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 executionRequest = executorRequest as ExecutionRequest\n\t\t\t\t\tconst executor = relationshipExecutor(\n\t\t\t\t\t\tschema,\n\t\t\t\t\t\texecutionRequest,\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\tgroupAccess,\n\t\t\t\t\t)\n\t\t\t\t\texecutor.preflight(table, selection, plan.args, route?.path ?? [])\n\t\t\t\t\tconst result = searchPlan\n\t\t\t\t\t\t? await executeSearch(\n\t\t\t\t\t\t\t\texecutionRequest,\n\t\t\t\t\t\t\t\ttable,\n\t\t\t\t\t\t\t\tsearchPlan,\n\t\t\t\t\t\t\t\tcodec,\n\t\t\t\t\t\t\t\tbinding,\n\t\t\t\t\t\t\t\tmaxPageSize,\n\t\t\t\t\t\t\t\tasync (row) => {\n\t\t\t\t\t\t\t\t\t// SAFETY: native search returns system fields from the admitted registry table.\n\t\t\t\t\t\t\t\t\tconst document = row as RelationDocument\n\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t!(await request.authorize(table, row)) ||\n\t\t\t\t\t\t\t\t\t\t!(await executor.matches(table, document, plan.residual))\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t\t\t\treturn executor.output(table, document, selection, plan.args)\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: route\n\t\t\t\t\t\t\t? await executor.continuation(\n\t\t\t\t\t\t\t\t\troute.table,\n\t\t\t\t\t\t\t\t\troute.parent,\n\t\t\t\t\t\t\t\t\troute.path,\n\t\t\t\t\t\t\t\t\tselection,\n\t\t\t\t\t\t\t\t\tplan.args,\n\t\t\t\t\t\t\t\t\tconditions,\n\t\t\t\t\t\t\t\t\tconstraints,\n\t\t\t\t\t\t\t\t\tordering,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: await executeQuery(\n\t\t\t\t\t\t\t\t\tschema,\n\t\t\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\t\t\ttable,\n\t\t\t\t\t\t\t\t\tplan,\n\t\t\t\t\t\t\t\t\tplanner,\n\t\t\t\t\t\t\t\t\tcodec,\n\t\t\t\t\t\t\t\t\tbinding,\n\t\t\t\t\t\t\t\t\tasync (row, permitted) => {\n\t\t\t\t\t\t\t\t\t\t// SAFETY: metered native stream rows contain the validated system fields.\n\t\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t\t!permitted ||\n\t\t\t\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\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\treturn null\n\t\t\t\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\t\t\t\treturn executor.output(table, row as RelationDocument, selection, plan.args)\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t// SAFETY: the private executor validated and projected every row against\n\t\t\t\t\t// this registry's DTO and selected edges before assembling the page.\n\t\t\t\t\treturn result as EnginePaginationResult<\n\t\t\t\t\t\tRelationQueryRow<Tables, D, keyof Tables, OutputSelection>\n\t\t\t\t\t>\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, {}, [1, 1, 1, 1], never, Schema> }\n\t}\n\treturn Object.assign(querying, {\n\t\ttables: relations.tables,\n\t\tcursorCodec<S extends z.ZodType<Value>>(\n\t\t\tctx: ManagedQueryContext<DataModelFromSchemaDefinition<Schema>>,\n\t\t\toptions: ScopedCursorOptions<S>,\n\t\t): ScopedCursorCodec<S> {\n\t\t\t// Admission precedes lazy key loading and all schema/codec operations.\n\t\t\tconst scope = security.cursorScope(ctx)\n\t\t\tcursorCodec ??= createEnvironmentCursorCodec()\n\t\t\treturn createScopedCursorCodec(cursorCodec.custom, scope, options)\n\t\t},\n\t})\n}\n\nfunction hasGroups(groups: ResolvedGroups): boolean {\n\treturn (\n\t\tObject.keys(groups.fieldGroups).length > 0 || Object.values(groups.relations).some(hasGroups)\n\t)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ErrorContract, ErrorDefinitions, UnknownFailure } from "../modules/contracts/errors.mjs";
|
|
2
|
+
//#region src/react/failures.d.ts
|
|
3
|
+
export type FailureContract = Pick<ErrorContract<ErrorDefinitions>, 'decode'>;
|
|
4
|
+
export type FailureOf<Contract extends FailureContract> = ReturnType<Contract['decode']>;
|
|
5
|
+
type DeclaredOf<Contract extends FailureContract> = Extract<FailureOf<Contract>, {
|
|
6
|
+
_tag: 'DeclaredFailure';
|
|
7
|
+
}>;
|
|
8
|
+
/** Extract ConvexError.data only. Contract validation owns the public envelope. */
|
|
9
|
+
export declare function decodeFailure<Contract extends FailureContract>(error: unknown, contract: Contract): FailureOf<Contract>;
|
|
10
|
+
type DeclaredHandlers<Contract extends FailureContract> = { [Code in DeclaredOf<Contract>['code']]: (failure: Code extends '_' ? FailureOf<Contract> : Extract<DeclaredOf<Contract>, {
|
|
11
|
+
code: Code;
|
|
12
|
+
}>) => unknown; };
|
|
13
|
+
export type FailureHandlers<Contract extends FailureContract> = DeclaredHandlers<Contract> | (Partial<DeclaredHandlers<Contract>> & {
|
|
14
|
+
_: (failure: FailureOf<Contract>) => unknown;
|
|
15
|
+
});
|
|
16
|
+
type HandlerResult<Handlers> = { [Key in keyof Handlers]: Handlers[Key] extends ((...args: never[]) => infer Result) ? Result : never; }[keyof Handlers];
|
|
17
|
+
/** Dispatch on code after validation. Without `_`, unknown errors return UnknownFailure. */
|
|
18
|
+
export declare function matchFailure<Contract extends FailureContract, const Handlers extends object>(error: unknown, contract: Contract, handlers: Partial<DeclaredHandlers<NoInfer<Contract>>> & {
|
|
19
|
+
_: (failure: FailureOf<NoInfer<Contract>>) => unknown;
|
|
20
|
+
} & Handlers & Record<Exclude<keyof Handlers, keyof DeclaredHandlers<Contract> | '_'>, never>): HandlerResult<Handlers>;
|
|
21
|
+
export declare function matchFailure<Contract extends FailureContract, const Handlers extends object>(error: unknown, contract: Contract, handlers: DeclaredHandlers<NoInfer<Contract>> & Handlers & Record<Exclude<keyof Handlers, keyof DeclaredHandlers<Contract>>, never>): HandlerResult<Handlers> | UnknownFailure;
|
|
22
|
+
//#endregion
|
|
23
|
+
//# sourceMappingURL=failures.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{ConvexError as e}from"convex/values";function t(t,n){let r={_tag:`UnknownFailure`,version:1};try{t instanceof e&&(r=n.decode(t.data))}catch{}return r}function n(e,n,r){let i=t(e,n),a=r,o=i._tag===`DeclaredFailure`&&Object.hasOwn(r,i.code)?a[i.code]:void 0;return o?o(i):Object.hasOwn(r,`_`)&&a._?a._(i):i}export{t as decodeFailure,n as matchFailure};
|
|
2
|
+
//# sourceMappingURL=failures.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failures.mjs","names":["decodeFailure","failure","error","ConvexError","contract","matchFailure","callbacks","handlers","handler"],"sources":["../../src/react/failures.ts"],"sourcesContent":["/* oxlint-disable anti-slop/no-unknown-parameters, anti-slop/no-unknown-returns -- Transport errors are untrusted; callback results are inferred independently. */\nimport { ConvexError } from 'convex/values'\nimport type { ErrorContract, ErrorDefinitions, UnknownFailure } from '../modules/contracts/errors'\n\nexport type FailureContract = Pick<ErrorContract<ErrorDefinitions>, 'decode'>\nexport type FailureOf<Contract extends FailureContract> = ReturnType<Contract['decode']>\ntype DeclaredOf<Contract extends FailureContract> = Extract<\n\tFailureOf<Contract>,\n\t{ _tag: 'DeclaredFailure' }\n>\n\n/** Extract ConvexError.data only. Contract validation owns the public envelope. */\nexport function decodeFailure<Contract extends FailureContract>(\n\terror: unknown,\n\tcontract: Contract,\n): FailureOf<Contract> {\n\tlet failure: ReturnType<FailureContract['decode']> = { _tag: 'UnknownFailure', version: 1 }\n\ttry {\n\t\tif (error instanceof ConvexError) failure = contract.decode(error.data)\n\t} catch {\n\t\t// Malformed proxies/getters and foreign errors never expose their diagnostics.\n\t}\n\t// SAFETY: the explicit contract owns its decode result; every error contract includes UnknownFailure.\n\treturn failure as FailureOf<Contract>\n}\n\ntype DeclaredHandlers<Contract extends FailureContract> = {\n\t[Code in DeclaredOf<Contract>['code']]: (\n\t\tfailure: Code extends '_' ? FailureOf<Contract> : Extract<DeclaredOf<Contract>, { code: Code }>,\n\t) => unknown\n}\nexport type FailureHandlers<Contract extends FailureContract> =\n\t| DeclaredHandlers<Contract>\n\t| (Partial<DeclaredHandlers<Contract>> & { _: (failure: FailureOf<Contract>) => unknown })\ntype HandlerResult<Handlers> = {\n\t[Key in keyof Handlers]: Handlers[Key] extends (...args: never[]) => infer Result ? Result : never\n}[keyof Handlers]\n\n/** Dispatch on code after validation. Without `_`, unknown errors return UnknownFailure. */\nexport function matchFailure<Contract extends FailureContract, const Handlers extends object>(\n\terror: unknown,\n\tcontract: Contract,\n\thandlers: Partial<DeclaredHandlers<NoInfer<Contract>>> & {\n\t\t_: (failure: FailureOf<NoInfer<Contract>>) => unknown\n\t} & Handlers &\n\t\tRecord<Exclude<keyof Handlers, keyof DeclaredHandlers<Contract> | '_'>, never>,\n): HandlerResult<Handlers>\nexport function matchFailure<Contract extends FailureContract, const Handlers extends object>(\n\terror: unknown,\n\tcontract: Contract,\n\thandlers: DeclaredHandlers<NoInfer<Contract>> &\n\t\tHandlers &\n\t\tRecord<Exclude<keyof Handlers, keyof DeclaredHandlers<Contract>>, never>,\n): HandlerResult<Handlers> | UnknownFailure\nexport function matchFailure<Contract extends FailureContract>(\n\terror: unknown,\n\tcontract: Contract,\n\thandlers: FailureHandlers<Contract>,\n): unknown {\n\tconst failure = decodeFailure(error, contract)\n\t// SAFETY: the checked declaration pairs each callback with its code; only the validated code selects it.\n\tconst callbacks = handlers as Record<\n\t\tstring,\n\t\t((failure: FailureOf<Contract>) => unknown) | undefined\n\t>\n\tconst handler =\n\t\tfailure._tag === 'DeclaredFailure' && Object.hasOwn(handlers, failure.code)\n\t\t\t? callbacks[failure.code]\n\t\t\t: undefined\n\treturn handler\n\t\t? handler(failure)\n\t\t: Object.hasOwn(handlers, '_') && callbacks._\n\t\t\t? callbacks._(failure)\n\t\t\t: failure\n}\n"],"mappings":"4CAYA,SAAgBA,EACf,EACA,EACsB,CACtB,IAAIC,EAAiD,CAAE,KAAM,iBAAkB,QAAS,CAAE,EAC1F,GAAI,CACCC,aAAiBC,IAAa,EAAUC,EAAS,OAAOF,EAAM,IAAI,EACvE,MAAQ,CAER,CAEA,OAAOD,CACR,CA8BA,SAAgBI,EACf,EACA,EACA,EACU,CACV,IAAMJ,EAAUD,EAAcE,EAAOE,CAAQ,EAEvCE,EAAYC,EAIZC,EACLP,EAAQ,OAAS,mBAAqB,OAAO,OAAOM,EAAUN,EAAQ,IAAI,EACvEK,EAAUL,EAAQ,MAClB,IAAA,GACJ,OAAOO,EACJA,EAAQP,CAAO,EACf,OAAO,OAAOM,EAAU,GAAG,GAAKD,EAAU,EACzCA,EAAU,EAAEL,CAAO,EACnBA,CACL"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { FilterAst } from "../querying/shared/ast.mjs";
|
|
2
|
+
import { FunctionArgs, FunctionReference, FunctionReturnType, PaginationOptions } from "convex/server";
|
|
3
|
+
import { Value } from "convex/values";
|
|
4
|
+
import { OptimisticLocalStore } from "convex/browser";
|
|
5
|
+
//#region src/react/optimistic.d.ts
|
|
6
|
+
type OptimisticQuery = FunctionReference<'query', 'public', {
|
|
7
|
+
paginationOpts: PaginationOptions;
|
|
8
|
+
}, {
|
|
9
|
+
page: Record<string, Value | undefined>[];
|
|
10
|
+
}>;
|
|
11
|
+
type RowOf<Query extends OptimisticQuery> = FunctionReturnType<Query>['page'][number];
|
|
12
|
+
type FieldOf<Query extends OptimisticQuery> = keyof RowOf<Query> & string;
|
|
13
|
+
export type OptimisticInterval = {
|
|
14
|
+
/** Bounds in declared sort order: lower exclusive, upper inclusive. Null attests an unbounded side. */
|
|
15
|
+
lower: readonly Value[] | null;
|
|
16
|
+
upper: readonly Value[] | null;
|
|
17
|
+
};
|
|
18
|
+
export type OptimisticOrder<Field extends string> = readonly {
|
|
19
|
+
field: Field;
|
|
20
|
+
direction: 'asc' | 'desc';
|
|
21
|
+
}[];
|
|
22
|
+
/** Shared server-owned semantics, never inferred from client filters or protected cursors. */
|
|
23
|
+
export type OptimisticListDescriptor<Query extends OptimisticQuery, Writable extends FieldOf<Query>, Id extends FieldOf<Query> = FieldOf<Query>> = {
|
|
24
|
+
id: Id;
|
|
25
|
+
writable: readonly Writable[];
|
|
26
|
+
/** Fields permitted by this args/session's output projection, including the ID. */
|
|
27
|
+
fields: (args: FunctionArgs<Query>) => readonly FieldOf<Query>[];
|
|
28
|
+
session: (args: FunctionArgs<Query>) => Value;
|
|
29
|
+
/** Known predicates may prove false even when other server logic remains undeclared. */
|
|
30
|
+
membership: (args: FunctionArgs<Query>) => {
|
|
31
|
+
predicate: FilterAst;
|
|
32
|
+
complete: boolean;
|
|
33
|
+
};
|
|
34
|
+
/** Complete, total server ordering, including tie breakers. Missing values remain unknown. */
|
|
35
|
+
order?: (args: FunctionArgs<Query>) => OptimisticOrder<FieldOf<Query>>;
|
|
36
|
+
/** An attested loaded interval, not a decoded cursor or a guessed first-page range. */
|
|
37
|
+
interval?: (args: FunctionArgs<Query>, page: FunctionReturnType<Query>) => OptimisticInterval | undefined;
|
|
38
|
+
};
|
|
39
|
+
export type OptimisticList<Query extends OptimisticQuery, Writable extends FieldOf<Query>, Id extends FieldOf<Query> = FieldOf<Query>> = {
|
|
40
|
+
patch: (store: OptimisticLocalStore, id: RowOf<Query>[Id], patch: Partial<Pick<RowOf<Query>, Writable>>) => void;
|
|
41
|
+
insert: (store: OptimisticLocalStore, row: RowOf<Query>) => void;
|
|
42
|
+
delete: (store: OptimisticLocalStore, id: RowOf<Query>[Id]) => void;
|
|
43
|
+
};
|
|
44
|
+
/** Call synchronously inside withOptimisticUpdate/useCommand. Convex owns replay and rollback. */
|
|
45
|
+
export declare function defineOptimisticList<Query extends OptimisticQuery, Writable extends FieldOf<Query>, Id extends FieldOf<Query>>(reference: Query, descriptor: OptimisticListDescriptor<Query, Writable, Id>): OptimisticList<Query, Writable, Id>;
|
|
46
|
+
//#endregion
|
|
47
|
+
//# sourceMappingURL=optimistic.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{evaluateFilter as e}from"../querying/shared/evaluate.mjs";import{compareValues as t,convexToJson as n}from"convex/values";function r(r,a){function o(o,s){let c=/* @__PURE__ */ new Map;for(let{args:e,value:t}of o.getAllQueries(r)){if(t===void 0)continue;let{paginationOpts:r,...i}=e,o=JSON.stringify(n([i,a.session(e)])),s=c.get(o)??[];s.push({args:e,value:t,rows:t.page}),c.set(o,s)}let l=s.kind===`insert`?s.row[a.id]:s.id,u=e=>t(e[a.id],l)===0;for(let l of c.values()){let c=l.flatMap(e=>e.rows.filter(u).map(t=>({entry:e,row:t})))[0],d,f=!1;if(s.kind===`insert`)c||(d=s.row);else if(s.kind===`patch`){for(let t of l){let n=new Set(a.fields(t.args));t.rows=t.rows.flatMap(r=>{if(!u(r))return[r];let i={...r};for(let e of a.writable)!Object.is(e,a.id)&&n.has(e)&&Object.hasOwn(r,e)&&Object.hasOwn(s.patch,e)&&(i[e]=s.patch[e]);let{predicate:o}=a.membership(t.args),c=new Set(Object.keys(r).filter(e=>n.has(e)));return e(o,i,c)===!1?[]:[i]})}if(c){let e=a.order?.(c.entry.args),n=c.entry.rows.find(u);n&&e?.length&&e.every(({field:e})=>Object.hasOwn(c.row,e)&&c.row[e]!==void 0&&a.fields(c.entry.args).includes(e))&&e.some(({field:e})=>t(c.row[e],n[e])!==0)&&(d=n)}}else for(let e of l)e.rows=e.rows.filter(e=>!u(e));if(d){let t=!1,n=l.flatMap(n=>{let r=new Set(a.fields(n.args)),o=n.value.page.find(u)?n.rows.find(u):d;if(!o)return[];let s=Object.fromEntries(Object.entries(o).filter(([e])=>r.has(e))),{predicate:c,complete:l}=a.membership(n.args),f=a.order?.(n.args),p=a.interval?.(n.args,n.value),m=new Set(Object.keys(s)),h=e(c,s,m);if(h===!1)return[];if(!m.has(a.id)||s[a.id]===void 0||!f?.length||!p||!f.every(({field:e})=>m.has(e)&&s[e]!==void 0))return t=!0,[];let g=f.map(({field:e})=>s[e]);return p.lower&&(p.lower.length!==f.length||i(g,p.lower,f)<=0)||p.upper&&(p.upper.length!==f.length||i(g,p.upper,f)>0)?[]:!l||h!==!0||!n.rows.every(e=>f.every(({field:t})=>r.has(t)&&Object.hasOwn(e,t)&&e[t]!==void 0))?(t=!0,[]):[{entry:n,projected:s,order:f}]});if(!t&&n.length===1){f=!0;let{entry:e,projected:t,order:r}=n[0];for(let e of l)e.rows=e.rows.filter(e=>!u(e));let a=e.rows.findIndex(e=>i(r.map(({field:e})=>t[e]),r.map(({field:t})=>e[t]),r)<0);e.rows=[...e.rows.slice(0,a<0?e.rows.length:a),t,...a<0?[]:e.rows.slice(a)]}}for(let e of l){let t=/* @__PURE__ */ new Set,i=e.rows.filter(e=>{if(!f&&(s.kind===`insert`||!u(e)))return!0;let r=JSON.stringify(n(e[a.id]??null));return!t.has(r)&&(t.add(r),!0)});i.length===e.value.page.length&&i.every((t,n)=>t===e.value.page[n])||o.setQuery(r,e.args,{...e.value,page:i})}}}return{patch:(e,t,n)=>o(e,{kind:`patch`,id:t,patch:n}),insert:(e,t)=>o(e,{kind:`insert`,row:t}),delete:(e,t)=>o(e,{kind:`delete`,id:t})}}function i(e,n,r){for(let i=0;i<r.length;i++){let a=t(e[i],n[i]);if(a)return r[i].direction===`asc`?a:-a}return 0}export{r as defineOptimisticList};
|
|
2
|
+
//# sourceMappingURL=optimistic.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimistic.mjs","names":["defineOptimisticList","apply","groups","store","reference","value","_pagination","rest","args","key","convexToJson","descriptor","group","id","change","matches","compareValues","row","source","entry","candidate","placed","fields","next","field","accessible","evaluateFilter","predicate","order","uncertainDestination","destinations","localCandidate","projected","interval","membership","compareKeys","complete","page","index","seen","rows","i","compared","left","right"],"sources":["../../src/react/optimistic.ts"],"sourcesContent":["import type { OptimisticLocalStore } from 'convex/browser'\nimport type {\n\tFunctionArgs,\n\tFunctionReference,\n\tFunctionReturnType,\n\tPaginationOptions,\n} from 'convex/server'\nimport { compareValues, convexToJson, type Value } from 'convex/values'\nimport { evaluateFilter } from '../querying/shared/evaluate'\nimport type { FilterAst } from '../querying/shared/ast'\n\ntype OptimisticQuery = FunctionReference<\n\t'query',\n\t'public',\n\t{ paginationOpts: PaginationOptions },\n\t{ page: Record<string, Value | undefined>[] }\n>\ntype RowOf<Query extends OptimisticQuery> = FunctionReturnType<Query>['page'][number]\ntype FieldOf<Query extends OptimisticQuery> = keyof RowOf<Query> & string\nexport type OptimisticInterval = {\n\t/** Bounds in declared sort order: lower exclusive, upper inclusive. Null attests an unbounded side. */\n\tlower: readonly Value[] | null\n\tupper: readonly Value[] | null\n}\nexport type OptimisticOrder<Field extends string> = readonly {\n\tfield: Field\n\tdirection: 'asc' | 'desc'\n}[]\n/** Shared server-owned semantics, never inferred from client filters or protected cursors. */\nexport type OptimisticListDescriptor<\n\tQuery extends OptimisticQuery,\n\tWritable extends FieldOf<Query>,\n\tId extends FieldOf<Query> = FieldOf<Query>,\n> = {\n\tid: Id\n\twritable: readonly Writable[]\n\t/** Fields permitted by this args/session's output projection, including the ID. */\n\tfields: (args: FunctionArgs<Query>) => readonly FieldOf<Query>[]\n\tsession: (args: FunctionArgs<Query>) => Value\n\t/** Known predicates may prove false even when other server logic remains undeclared. */\n\tmembership: (args: FunctionArgs<Query>) => { predicate: FilterAst; complete: boolean }\n\t/** Complete, total server ordering, including tie breakers. Missing values remain unknown. */\n\torder?: (args: FunctionArgs<Query>) => OptimisticOrder<FieldOf<Query>>\n\t/** An attested loaded interval, not a decoded cursor or a guessed first-page range. */\n\tinterval?: (\n\t\targs: FunctionArgs<Query>,\n\t\tpage: FunctionReturnType<Query>,\n\t) => OptimisticInterval | undefined\n}\nexport type OptimisticList<\n\tQuery extends OptimisticQuery,\n\tWritable extends FieldOf<Query>,\n\tId extends FieldOf<Query> = FieldOf<Query>,\n> = {\n\tpatch: (\n\t\tstore: OptimisticLocalStore,\n\t\tid: RowOf<Query>[Id],\n\t\tpatch: Partial<Pick<RowOf<Query>, Writable>>,\n\t) => void\n\tinsert: (store: OptimisticLocalStore, row: RowOf<Query>) => void\n\tdelete: (store: OptimisticLocalStore, id: RowOf<Query>[Id]) => void\n}\n\n/** Call synchronously inside withOptimisticUpdate/useCommand. Convex owns replay and rollback. */\nexport function defineOptimisticList<\n\tQuery extends OptimisticQuery,\n\tWritable extends FieldOf<Query>,\n\tId extends FieldOf<Query>,\n>(\n\treference: Query,\n\tdescriptor: OptimisticListDescriptor<Query, Writable, Id>,\n): OptimisticList<Query, Writable, Id> {\n\ttype Row = RowOf<Query>\n\ttype Change =\n\t\t| { kind: 'patch'; id: Row[Id]; patch: Partial<Pick<Row, Writable>> }\n\t\t| { kind: 'insert'; row: Row }\n\t\t| { kind: 'delete'; id: Row[Id] }\n\tfunction apply(store: OptimisticLocalStore, change: Change): void {\n\t\tconst groups = new Map<\n\t\t\tstring,\n\t\t\t{ args: FunctionArgs<Query>; value: FunctionReturnType<Query>; rows: Row[] }[]\n\t\t>()\n\t\tfor (const { args, value } of store.getAllQueries(reference)) {\n\t\t\tif (value === undefined) continue\n\t\t\tconst { paginationOpts: _pagination, ...rest } = args\n\t\t\t// SAFETY: Convex query arguments contain only Convex values; only pagination transport was omitted.\n\t\t\tconst key = JSON.stringify(\n\t\t\t\tconvexToJson([rest as Record<string, Value>, descriptor.session(args)]),\n\t\t\t)\n\t\t\tconst group = groups.get(key) ?? []\n\t\t\tgroup.push({ args, value, rows: value.page })\n\t\t\tgroups.set(key, group)\n\t\t}\n\t\tconst id = change.kind === 'insert' ? change.row[descriptor.id] : change.id\n\t\tconst matches = (row: Row) => compareValues(row[descriptor.id], id) === 0\n\t\tfor (const group of groups.values()) {\n\t\t\tconst source = group.flatMap((entry) =>\n\t\t\t\tentry.rows.filter(matches).map((row) => ({ entry, row })),\n\t\t\t)[0]\n\t\t\tlet candidate: Row | undefined\n\t\t\tlet placed = false\n\t\t\tif (change.kind === 'insert') {\n\t\t\t\tif (!source) candidate = change.row\n\t\t\t} else if (change.kind === 'patch') {\n\t\t\t\tfor (const entry of group) {\n\t\t\t\t\tconst fields = new Set(descriptor.fields(entry.args))\n\t\t\t\t\tentry.rows = entry.rows.flatMap((row) => {\n\t\t\t\t\t\tif (!matches(row)) return [row]\n\t\t\t\t\t\tconst next = { ...row }\n\t\t\t\t\t\tfor (const field of descriptor.writable) {\n\t\t\t\t\t\t\t// A patch cannot turn a hidden/absent output field into knowledge or expose an ID change.\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t!Object.is(field, descriptor.id) &&\n\t\t\t\t\t\t\t\tfields.has(field) &&\n\t\t\t\t\t\t\t\tObject.hasOwn(row, field) &&\n\t\t\t\t\t\t\t\tObject.hasOwn(change.patch, field)\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tnext[field] = change.patch[field]!\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst { predicate } = descriptor.membership(entry.args)\n\t\t\t\t\t\tconst accessible = new Set(Object.keys(row).filter((field) => fields.has(field)))\n\t\t\t\t\t\tif (evaluateFilter(predicate, next, accessible) === false) return []\n\t\t\t\t\t\treturn [next]\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tif (source) {\n\t\t\t\t\tconst order = descriptor.order?.(source.entry.args)\n\t\t\t\t\tconst next = source.entry.rows.find(matches)\n\t\t\t\t\t// Never infer rank from a field that was absent before the patch.\n\t\t\t\t\tif (\n\t\t\t\t\t\tnext &&\n\t\t\t\t\t\torder?.length &&\n\t\t\t\t\t\torder.every(\n\t\t\t\t\t\t\t({ field }) =>\n\t\t\t\t\t\t\t\tObject.hasOwn(source.row, field) &&\n\t\t\t\t\t\t\t\tsource.row[field] !== undefined &&\n\t\t\t\t\t\t\t\tdescriptor.fields(source.entry.args).includes(field),\n\t\t\t\t\t\t) &&\n\t\t\t\t\t\torder.some(({ field }) => compareValues(source.row[field], next[field]) !== 0)\n\t\t\t\t\t)\n\t\t\t\t\t\tcandidate = next\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor (const entry of group) entry.rows = entry.rows.filter((row) => !matches(row))\n\t\t\t}\n\t\t\tif (candidate) {\n\t\t\t\tlet uncertainDestination = false\n\t\t\t\tconst destinations = group.flatMap((entry) => {\n\t\t\t\t\tconst fields = new Set(descriptor.fields(entry.args))\n\t\t\t\t\tconst existing = entry.value.page.find(matches)\n\t\t\t\t\t// An existing copy owns its field knowledge, including fields absent through redaction.\n\t\t\t\t\tconst localCandidate = existing ? entry.rows.find(matches) : candidate\n\t\t\t\t\tif (!localCandidate) return []\n\t\t\t\t\t// SAFETY: selecting only permitted DTO fields preserves their original values and never merges projections.\n\t\t\t\t\tconst projected = Object.fromEntries(\n\t\t\t\t\t\tObject.entries(localCandidate).filter(([field]) => fields.has(field)),\n\t\t\t\t\t) as Row\n\t\t\t\t\tconst { predicate, complete } = descriptor.membership(entry.args)\n\t\t\t\t\tconst order = descriptor.order?.(entry.args)\n\t\t\t\t\tconst interval = descriptor.interval?.(entry.args, entry.value)\n\t\t\t\t\tconst accessible = new Set(Object.keys(projected))\n\t\t\t\t\tconst membership = evaluateFilter(predicate, projected, accessible)\n\t\t\t\t\tif (membership === false) return []\n\t\t\t\t\tif (\n\t\t\t\t\t\t!accessible.has(descriptor.id) ||\n\t\t\t\t\t\tprojected[descriptor.id] === undefined ||\n\t\t\t\t\t\t!order?.length ||\n\t\t\t\t\t\t!interval ||\n\t\t\t\t\t\t!order.every(({ field }) => accessible.has(field) && projected[field] !== undefined)\n\t\t\t\t\t) {\n\t\t\t\t\t\tuncertainDestination = true\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\tconst key = order.map(({ field }) => projected[field]!)\n\t\t\t\t\tif (\n\t\t\t\t\t\t(interval.lower &&\n\t\t\t\t\t\t\t(interval.lower.length !== order.length ||\n\t\t\t\t\t\t\t\tcompareKeys(key, interval.lower, order) <= 0)) ||\n\t\t\t\t\t\t(interval.upper &&\n\t\t\t\t\t\t\t(interval.upper.length !== order.length ||\n\t\t\t\t\t\t\t\tcompareKeys(key, interval.upper, order) > 0))\n\t\t\t\t\t)\n\t\t\t\t\t\treturn []\n\t\t\t\t\tif (\n\t\t\t\t\t\t!complete ||\n\t\t\t\t\t\tmembership !== true ||\n\t\t\t\t\t\t!entry.rows.every((row) =>\n\t\t\t\t\t\t\torder.every(\n\t\t\t\t\t\t\t\t({ field }) =>\n\t\t\t\t\t\t\t\t\tfields.has(field) && Object.hasOwn(row, field) && row[field] !== undefined,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t) {\n\t\t\t\t\t\tuncertainDestination = true\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t\treturn [{ entry, projected, order }]\n\t\t\t\t})\n\t\t\t\t// Overlapping parent/split subscriptions are not evidence of a unique destination.\n\t\t\t\tif (!uncertainDestination && destinations.length === 1) {\n\t\t\t\t\tplaced = true\n\t\t\t\t\tconst { entry, projected, order } = destinations[0]\n\t\t\t\t\tfor (const page of group) page.rows = page.rows.filter((row) => !matches(row))\n\t\t\t\t\tconst index = entry.rows.findIndex(\n\t\t\t\t\t\t(row) =>\n\t\t\t\t\t\t\tcompareKeys(\n\t\t\t\t\t\t\t\torder.map(({ field }) => projected[field]!),\n\t\t\t\t\t\t\t\torder.map(({ field }) => row[field]!),\n\t\t\t\t\t\t\t\torder,\n\t\t\t\t\t\t\t) < 0,\n\t\t\t\t\t)\n\t\t\t\t\tentry.rows = [\n\t\t\t\t\t\t...entry.rows.slice(0, index < 0 ? entry.rows.length : index),\n\t\t\t\t\t\tprojected,\n\t\t\t\t\t\t...(index < 0 ? [] : entry.rows.slice(index)),\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const entry of group) {\n\t\t\t\tconst seen = new Set<string>()\n\t\t\t\tconst rows = entry.rows.filter((row) => {\n\t\t\t\t\tif (!placed && (change.kind === 'insert' || !matches(row))) return true\n\t\t\t\t\tconst key = JSON.stringify(convexToJson(row[descriptor.id] ?? null))\n\t\t\t\t\tif (seen.has(key)) return false\n\t\t\t\t\tseen.add(key)\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t\tif (\n\t\t\t\t\trows.length === entry.value.page.length &&\n\t\t\t\t\trows.every((row, i) => row === entry.value.page[i])\n\t\t\t\t)\n\t\t\t\t\tcontinue\n\t\t\t\tstore.setQuery(reference, entry.args, { ...entry.value, page: rows })\n\t\t\t}\n\t\t}\n\t}\n\treturn {\n\t\tpatch: (store, id, patch) => apply(store, { kind: 'patch', id, patch }),\n\t\tinsert: (store, row) => apply(store, { kind: 'insert', row }),\n\t\tdelete: (store, id) => apply(store, { kind: 'delete', id }),\n\t}\n}\nfunction compareKeys(\n\tleft: readonly Value[],\n\tright: readonly Value[],\n\torder: OptimisticOrder<string>,\n): number {\n\tfor (let index = 0; index < order.length; index++) {\n\t\tconst compared = compareValues(left[index], right[index])\n\t\tif (compared) return order[index].direction === 'asc' ? compared : -compared\n\t}\n\treturn 0\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/react/pagination-state.d.ts
|
|
2
|
+
/** Pure interval controller. Keys include the epoch so late subscription results cannot enter a reset. */
|
|
3
|
+
export type ListPage = {
|
|
4
|
+
page: unknown[];
|
|
5
|
+
continueCursor: string;
|
|
6
|
+
isDone: boolean;
|
|
7
|
+
completeness?: string;
|
|
8
|
+
splitCursor?: string | null;
|
|
9
|
+
pageStatus?: 'SplitRecommended' | 'SplitRequired' | null;
|
|
10
|
+
paginationMode?: 'native-search';
|
|
11
|
+
};
|
|
12
|
+
type Interval<Page extends ListPage> = {
|
|
13
|
+
cursor: string | null;
|
|
14
|
+
endCursor?: string;
|
|
15
|
+
value?: Page;
|
|
16
|
+
children?: [string, string];
|
|
17
|
+
};
|
|
18
|
+
export type PaginationState<Page extends ListPage> = {
|
|
19
|
+
epoch: number;
|
|
20
|
+
identity: string;
|
|
21
|
+
restarted: boolean;
|
|
22
|
+
next: number;
|
|
23
|
+
order: string[];
|
|
24
|
+
intervals: Record<string, Interval<Page>>;
|
|
25
|
+
mode?: 'browse' | 'native-search';
|
|
26
|
+
error?: Error;
|
|
27
|
+
};
|
|
28
|
+
export declare function initialPagination<Page extends ListPage>(identity: string, epoch: number, skip: boolean, restarted?: boolean): PaginationState<Page>;
|
|
29
|
+
export declare function failPagination<Page extends ListPage>(state: PaginationState<Page>, error: Error): PaginationState<Page>;
|
|
30
|
+
export declare function reconcilePagination<Page extends ListPage>(state: PaginationState<Page>, results: Record<string, Page | Error | undefined>): PaginationState<Page>;
|
|
31
|
+
export declare function paginationView<Page extends ListPage>(state: PaginationState<Page>): {
|
|
32
|
+
rows: Page["page"][number][];
|
|
33
|
+
completeness: string | undefined;
|
|
34
|
+
isLoading: boolean;
|
|
35
|
+
isFetchingNextPage: boolean;
|
|
36
|
+
hasNextPage: boolean;
|
|
37
|
+
};
|
|
38
|
+
export declare function nextPagination<Page extends ListPage>(state: PaginationState<Page>): PaginationState<Page>;
|
|
39
|
+
//#endregion
|
|
40
|
+
//# sourceMappingURL=pagination-state.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t,n,r=!1){let i=`${t}:0`;return{identity:e,epoch:t,restarted:r,next:1,order:n?[]:[i],intervals:n?{}:{[i]:{cursor:null}}}}function t(e,t){return{...e,order:[],intervals:{},error:t}}function n(e,n){if(e.error)return e;let r=!1,i={...e.intervals};for(let a of Object.keys(i)){let o=n[a];if(o instanceof Error)return t(e,o);o!==void 0&&o!==i[a].value&&(i[a]={...i[a],value:o},r=!0)}let a=i[e.order[0]]?.value,o=e.mode??(a?a.paginationMode??`browse`:void 0);for(let n of Object.values(i)){let r=n.value;if(r&&(r.paginationMode!==void 0&&r.paginationMode!==`native-search`||o!==void 0&&(r.paginationMode??`browse`)!==o||r.completeness===`searchSplitRequired`||o===`native-search`&&(r.pageStatus!=null||r.splitCursor!=null)))return t(e,/* @__PURE__ */ Error(`Invalid pagination protocol`))}let s=e.order,c=e.next;for(let n of e.order){let a=i[n],o=a.value;if(o){if(a.children)a.children.every(e=>i[e].value!==void 0)&&(s=s.flatMap(e=>e===n?a.children:[e]),delete i[n],r=!0);else if(o.pageStatus===`SplitRecommended`||o.pageStatus===`SplitRequired`){let s=o.splitCursor;if(!s||s===a.cursor||s===o.continueCursor||s===a.endCursor||o.continueCursor===a.cursor)return t(e,/* @__PURE__ */ Error(`Non-progressing pagination split`));let l=[`${e.epoch}:${c++}`,`${e.epoch}:${c++}`];i[n]={...a,children:l},i[l[0]]={cursor:a.cursor,endCursor:s},i[l[1]]={cursor:s,endCursor:a.endCursor??o.continueCursor},r=!0}else if(!o.isDone&&(!o.continueCursor||o.continueCursor===a.cursor))return t(e,/* @__PURE__ */ Error(`Non-progressing pagination cursor`))}}return r?{...e,mode:o,next:c,order:s,intervals:i}:e}function r(e){let t=[],n,r=!1;for(let i of e.order){let a=e.intervals[i];if(!a.value){r=!0;break}t.push(...a.value.page),n=a.value}let i=Object.values(e.intervals).some(e=>e.children!==void 0);return{rows:t,completeness:n?.completeness,isLoading:!e.error&&r&&n===void 0,isFetchingNextPage:!e.error&&(r&&n!==void 0||i),hasNextPage:!e.error&&!r&&!i&&n!==void 0&&!n.isDone}}function i(e){if(!r(e).hasNextPage)return e;let t=e.order[e.order.length-1],n=e.intervals[t],i=n.endCursor??n.value.continueCursor,a=`${e.epoch}:${e.next}`;return{...e,next:e.next+1,order:[...e.order,a],intervals:{...e.intervals,[t]:e.mode===`native-search`?n:{...n,endCursor:i},[a]:{cursor:i}}}}export{t as failPagination,e as initialPagination,i as nextPagination,r as paginationView,n as reconcilePagination};
|
|
2
|
+
//# sourceMappingURL=pagination-state.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination-state.mjs","names":["initialPagination","key","epoch","skip","failPagination","state","reconcilePagination","changed","intervals","value","results","first","mode","interval","order","next","child","item","split","children","paginationView","rows","latest","loading","splitting","nextPagination","lastKey","last","endCursor"],"sources":["../../src/react/pagination-state.ts"],"sourcesContent":["/** Pure interval controller. Keys include the epoch so late subscription results cannot enter a reset. */\nexport type ListPage = {\n\tpage: unknown[]\n\tcontinueCursor: string\n\tisDone: boolean\n\tcompleteness?: string\n\tsplitCursor?: string | null\n\tpageStatus?: 'SplitRecommended' | 'SplitRequired' | null\n\tpaginationMode?: 'native-search'\n}\ntype Interval<Page extends ListPage> = {\n\tcursor: string | null\n\tendCursor?: string\n\tvalue?: Page\n\tchildren?: [string, string]\n}\nexport type PaginationState<Page extends ListPage> = {\n\tepoch: number\n\tidentity: string\n\trestarted: boolean\n\tnext: number\n\torder: string[]\n\tintervals: Record<string, Interval<Page>>\n\tmode?: 'browse' | 'native-search'\n\terror?: Error\n}\nexport function initialPagination<Page extends ListPage>(\n\tidentity: string,\n\tepoch: number,\n\tskip: boolean,\n\trestarted = false,\n): PaginationState<Page> {\n\tconst key = `${epoch}:0`\n\treturn {\n\t\tidentity,\n\t\tepoch,\n\t\trestarted,\n\t\tnext: 1,\n\t\torder: skip ? [] : [key],\n\t\tintervals: skip ? {} : { [key]: { cursor: null } },\n\t}\n}\nexport function failPagination<Page extends ListPage>(\n\tstate: PaginationState<Page>,\n\terror: Error,\n): PaginationState<Page> {\n\treturn { ...state, order: [], intervals: {}, error }\n}\nexport function reconcilePagination<Page extends ListPage>(\n\tstate: PaginationState<Page>,\n\tresults: Record<string, Page | Error | undefined>,\n): PaginationState<Page> {\n\tif (state.error) return state\n\tlet changed = false\n\tconst intervals = { ...state.intervals }\n\tfor (const key of Object.keys(intervals)) {\n\t\tconst value = results[key]\n\t\tif (value instanceof Error) return failPagination(state, value)\n\t\tif (value !== undefined && value !== intervals[key].value) {\n\t\t\tintervals[key] = { ...intervals[key], value }\n\t\t\tchanged = true\n\t\t}\n\t}\n\t// The first result chooses the protocol for this epoch, including on reactive reruns.\n\tconst first = intervals[state.order[0]]?.value\n\tconst mode = state.mode ?? (first ? (first.paginationMode ?? 'browse') : undefined)\n\tfor (const interval of Object.values(intervals)) {\n\t\tconst value = interval.value\n\t\tif (!value) continue\n\t\tif (\n\t\t\t(value.paginationMode !== undefined && value.paginationMode !== 'native-search') ||\n\t\t\t(mode !== undefined && (value.paginationMode ?? 'browse') !== mode) ||\n\t\t\tvalue.completeness === 'searchSplitRequired' ||\n\t\t\t(mode === 'native-search' && (value.pageStatus != null || value.splitCursor != null))\n\t\t)\n\t\t\treturn failPagination(state, new Error('Invalid pagination protocol'))\n\t}\n\tlet order = state.order,\n\t\tnext = state.next\n\tfor (const key of state.order) {\n\t\tconst interval = intervals[key],\n\t\t\tvalue = interval.value\n\t\tif (!value) continue\n\t\tif (interval.children) {\n\t\t\tif (interval.children.every((child) => intervals[child].value !== undefined)) {\n\t\t\t\torder = order.flatMap((item) => (item === key ? interval.children! : [item]))\n\t\t\t\tdelete intervals[key]\n\t\t\t\tchanged = true\n\t\t\t}\n\t\t} else if (value.pageStatus === 'SplitRecommended' || value.pageStatus === 'SplitRequired') {\n\t\t\tconst split = value.splitCursor\n\t\t\tif (\n\t\t\t\t!split ||\n\t\t\t\tsplit === interval.cursor ||\n\t\t\t\tsplit === value.continueCursor ||\n\t\t\t\tsplit === interval.endCursor ||\n\t\t\t\tvalue.continueCursor === interval.cursor\n\t\t\t)\n\t\t\t\treturn failPagination(state, new Error('Non-progressing pagination split'))\n\t\t\tconst children: [string, string] = [`${state.epoch}:${next++}`, `${state.epoch}:${next++}`]\n\t\t\tintervals[key] = { ...interval, children }\n\t\t\tintervals[children[0]] = { cursor: interval.cursor, endCursor: split }\n\t\t\tintervals[children[1]] = {\n\t\t\t\tcursor: split,\n\t\t\t\tendCursor: interval.endCursor ?? value.continueCursor,\n\t\t\t}\n\t\t\tchanged = true\n\t\t} else if (\n\t\t\t!value.isDone &&\n\t\t\t(!value.continueCursor || value.continueCursor === interval.cursor)\n\t\t) {\n\t\t\treturn failPagination(state, new Error('Non-progressing pagination cursor'))\n\t\t}\n\t}\n\treturn changed ? { ...state, mode, next, order, intervals } : state\n}\nexport function paginationView<Page extends ListPage>(state: PaginationState<Page>) {\n\tconst rows: Page['page'][number][] = []\n\tlet latest: Page | undefined\n\tlet loading = false\n\tfor (const key of state.order) {\n\t\tconst interval = state.intervals[key]\n\t\tif (!interval.value) {\n\t\t\tloading = true\n\t\t\tbreak\n\t\t}\n\t\trows.push(...interval.value.page)\n\t\tlatest = interval.value\n\t}\n\tconst splitting = Object.values(state.intervals).some(\n\t\t(interval) => interval.children !== undefined,\n\t)\n\treturn {\n\t\trows,\n\t\tcompleteness: latest?.completeness,\n\t\tisLoading: !state.error && loading && latest === undefined,\n\t\tisFetchingNextPage: !state.error && ((loading && latest !== undefined) || splitting),\n\t\thasNextPage: !state.error && !loading && !splitting && latest !== undefined && !latest.isDone,\n\t}\n}\nexport function nextPagination<Page extends ListPage>(\n\tstate: PaginationState<Page>,\n): PaginationState<Page> {\n\tif (!paginationView(state).hasNextPage) return state\n\tconst lastKey = state.order[state.order.length - 1]\n\tconst last = state.intervals[lastKey]\n\tconst endCursor = last.endCursor ?? last.value!.continueCursor\n\tconst key = `${state.epoch}:${state.next}`\n\treturn {\n\t\t...state,\n\t\tnext: state.next + 1,\n\t\torder: [...state.order, key],\n\t\tintervals: {\n\t\t\t...state.intervals,\n\t\t\t// Fixed adjacent ranges keep reactive inserts/deletes from moving rows across a loaded boundary.\n\t\t\t// Native search windows are maintained by SDK journals; their original inputs stay immutable.\n\t\t\t[lastKey]: state.mode === 'native-search' ? last : { ...last, endCursor },\n\t\t\t[key]: { cursor: endCursor },\n\t\t},\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FailureContract, FailureOf } from "./failures.mjs";
|
|
2
|
+
import { FunctionArgs, FunctionReference, FunctionReturnType, OptionalRestArgs } from "convex/server";
|
|
3
|
+
import { OptimisticUpdate } from "convex/browser";
|
|
4
|
+
//#region src/react/use-command.d.ts
|
|
5
|
+
export type CommandResult<Result, Contract extends FailureContract> = Readonly<{
|
|
6
|
+
ok: true;
|
|
7
|
+
value: Result;
|
|
8
|
+
}> | Readonly<{
|
|
9
|
+
ok: false;
|
|
10
|
+
failure: FailureOf<Contract>;
|
|
11
|
+
}>;
|
|
12
|
+
export type Command<Mutation extends FunctionReference<'mutation'>, Contract extends FailureContract> = ((...args: OptionalRestArgs<Mutation>) => Promise<CommandResult<FunctionReturnType<Mutation>, Contract>>) & {
|
|
13
|
+
readonly isPending: boolean;
|
|
14
|
+
/** The latest invocation's settled result; older concurrent completions cannot overwrite it. */
|
|
15
|
+
readonly result: CommandResult<FunctionReturnType<Mutation>, Contract> | undefined;
|
|
16
|
+
};
|
|
17
|
+
/** Explicit failures plus Convex-owned optimistic execution, replay and rollback. */
|
|
18
|
+
export declare function useCommand<Mutation extends FunctionReference<'mutation'>, Contract extends FailureContract, Update extends OptimisticUpdate<FunctionArgs<Mutation>> = OptimisticUpdate<FunctionArgs<Mutation>>>(reference: Mutation, options: {
|
|
19
|
+
errors: Contract;
|
|
20
|
+
optimisticUpdate?: Update & (ReturnType<Update> extends Promise<any> ? 'Optimistic update handlers must be synchronous' : {});
|
|
21
|
+
}): Command<Mutation, Contract>;
|
|
22
|
+
//#endregion
|
|
23
|
+
//# sourceMappingURL=use-command.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{decodeFailure as e}from"./failures.mjs";import{useCallback as t,useMemo as n,useRef as r,useState as i}from"react";import{useMutation as a}from"convex/react";function o(o,s){let c=a(o),{errors:l,optimisticUpdate:u}=s,d=n(()=>u?c.withOptimisticUpdate(u):c,[c,u]),[f,p]=i(0),[m,h]=i(),g=r(0),_=t(async(...t)=>{let n=++g.current;p(e=>e+1);let r;try{r={ok:!0,value:await d(...t)}}catch(t){r={ok:!1,failure:e(t,l)}}return p(e=>e-1),g.current===n&&h(r),r},[d,l]);return n(()=>Object.assign((...e)=>_(...e),{isPending:f>0,result:m}),[_,f,m])}export{o as useCommand};
|
|
2
|
+
//# sourceMappingURL=use-command.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-command.mjs","names":["useCommand","mutation","useMutation","reference","options","run","useMemo","optimisticUpdate","pending","setPending","useState","result","setResult","latest","useRef","execute","useCallback","args","invocation","count","settled","error","decodeFailure","errors"],"sources":["../../src/react/use-command.ts"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react'\nimport { useMutation } from 'convex/react'\nimport type { OptimisticUpdate } from 'convex/browser'\nimport type {\n\tFunctionArgs,\n\tFunctionReference,\n\tFunctionReturnType,\n\tOptionalRestArgs,\n} from 'convex/server'\nimport { decodeFailure, type FailureContract, type FailureOf } from './failures'\n\nexport type CommandResult<Result, Contract extends FailureContract> =\n\t| Readonly<{ ok: true; value: Result }>\n\t| Readonly<{ ok: false; failure: FailureOf<Contract> }>\nexport type Command<\n\tMutation extends FunctionReference<'mutation'>,\n\tContract extends FailureContract,\n> = ((\n\t...args: OptionalRestArgs<Mutation>\n) => Promise<CommandResult<FunctionReturnType<Mutation>, Contract>>) & {\n\treadonly isPending: boolean\n\t/** The latest invocation's settled result; older concurrent completions cannot overwrite it. */\n\treadonly result: CommandResult<FunctionReturnType<Mutation>, Contract> | undefined\n}\n\n/** Explicit failures plus Convex-owned optimistic execution, replay and rollback. */\nexport function useCommand<\n\tMutation extends FunctionReference<'mutation'>,\n\tContract extends FailureContract,\n\tUpdate extends OptimisticUpdate<FunctionArgs<Mutation>> = OptimisticUpdate<\n\t\tFunctionArgs<Mutation>\n\t>,\n>(\n\treference: Mutation,\n\toptions: {\n\t\terrors: Contract\n\t\toptimisticUpdate?: Update &\n\t\t\t(ReturnType<Update> extends Promise<any>\n\t\t\t\t? 'Optimistic update handlers must be synchronous'\n\t\t\t\t: {})\n\t},\n): Command<Mutation, Contract> {\n\tconst mutation = useMutation(reference)\n\tconst { errors, optimisticUpdate } = options\n\t// Attach once per mutation/callback identity. Convex may invoke this callback repeatedly while pending.\n\tconst run = useMemo(\n\t\t() => (optimisticUpdate ? mutation.withOptimisticUpdate<Update>(optimisticUpdate) : mutation),\n\t\t[mutation, optimisticUpdate],\n\t)\n\tconst [pending, setPending] = useState(0)\n\tconst [result, setResult] = useState<CommandResult<FunctionReturnType<Mutation>, Contract>>()\n\tconst latest = useRef(0)\n\tconst execute = useCallback(\n\t\tasync (...args: OptionalRestArgs<Mutation>) => {\n\t\t\tconst invocation = ++latest.current\n\t\t\tsetPending((count) => count + 1)\n\t\t\tlet settled: CommandResult<FunctionReturnType<Mutation>, Contract>\n\t\t\ttry {\n\t\t\t\tsettled = { ok: true, value: await run(...args) }\n\t\t\t} catch (error) {\n\t\t\t\tsettled = { ok: false, failure: decodeFailure(error, errors) }\n\t\t\t}\n\t\t\tsetPending((count) => count - 1)\n\t\t\tif (latest.current === invocation) setResult(settled)\n\t\t\treturn settled\n\t\t},\n\t\t[run, errors],\n\t)\n\treturn useMemo(\n\t\t() =>\n\t\t\tObject.assign((...args: OptionalRestArgs<Mutation>) => execute(...args), {\n\t\t\t\tisPending: pending > 0,\n\t\t\t\tresult,\n\t\t\t}),\n\t\t[execute, pending, result],\n\t)\n}\n"],"mappings":"qKA0BA,SAAgBA,EAOf,EACA,EAO8B,CAC9B,IAAMC,EAAWC,EAAYC,CAAS,EAChC,CAAE,SAAQ,oBAAqBC,EAE/BC,EAAMC,MACJC,EAAmBN,EAAS,qBAA6BM,CAAgB,EAAIN,EACpF,CAACA,EAAUM,CAAgB,CAC5B,EACM,CAACC,EAASC,GAAcC,EAAS,CAAC,EAClC,CAACC,EAAQC,GAAaF,EAAgE,EACtFG,EAASC,EAAO,CAAC,EACjBC,EAAUC,EACf,MAAO,GAAGC,IAAqC,CAC9C,IAAMC,EAAa,EAAEL,EAAO,QAC5BJ,EAAY,GAAUU,EAAQ,CAAC,EAC/B,IAAIC,EACJ,GAAI,CACH,EAAU,CAAE,GAAI,GAAM,MAAO,MAAMf,EAAI,GAAGY,CAAI,CAAE,CACjD,OAASI,EAAO,CACf,EAAU,CAAE,GAAI,GAAO,QAASC,EAAcD,EAAOE,CAAM,CAAE,CAC9D,CAGA,OAFAd,EAAY,GAAUU,EAAQ,CAAC,EAC3BN,EAAO,UAAYK,GAAYN,EAAUQ,CAAO,EAC7CA,CACR,EACA,CAACf,EAAKkB,CAAM,CACb,EACA,OAAOjB,MAEL,OAAO,QAAQ,GAAGW,IAAqCF,EAAQ,GAAGE,CAAI,EAAG,CACxE,UAAWT,EAAU,EACrB,QACD,CAAC,EACF,CAACO,EAASP,EAASG,CAAM,CAC1B,CACD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { queryErrors } from "../modules/contracts/errors.mjs";
|
|
2
|
+
import { FailureContract, FailureOf } from "./failures.mjs";
|
|
3
|
+
import { ListPage } from "./pagination-state.mjs";
|
|
4
|
+
import { FunctionArgs, FunctionReference, FunctionReturnType, PaginationOptions } from "convex/server";
|
|
5
|
+
import { Value } from "convex/values";
|
|
6
|
+
//#region src/react/use-paginated-list.d.ts
|
|
7
|
+
export type ListQueryReference = FunctionReference<'query', 'public', {
|
|
8
|
+
paginationOpts: PaginationOptions;
|
|
9
|
+
}, ListPage>;
|
|
10
|
+
export type PaginatedList<Query extends ListQueryReference, Contract extends FailureContract = typeof queryErrors> = {
|
|
11
|
+
rows: FunctionReturnType<Query>['page'];
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
hasNextPage: boolean;
|
|
14
|
+
isFetchingNextPage: boolean;
|
|
15
|
+
fetchNextPage: () => void;
|
|
16
|
+
resetKey: string;
|
|
17
|
+
completeness: FunctionReturnType<Query>['completeness'] | undefined;
|
|
18
|
+
failure: FailureOf<Contract> | undefined;
|
|
19
|
+
};
|
|
20
|
+
export type PaginatedListOptions<Contract extends FailureContract = typeof queryErrors> = {
|
|
21
|
+
/** Integer 1..100. Initial target for native search; browse uses fixed adjacent pages. */
|
|
22
|
+
pageSize: number;
|
|
23
|
+
onInvalidCursor?: 'restart' | 'throw';
|
|
24
|
+
/** Explicit server contract; generated references do not carry error metadata. Defaults to queryErrors. */
|
|
25
|
+
errors?: Contract;
|
|
26
|
+
/** Convex value identifying the current auth/scope/reset generation. Never a credential. */
|
|
27
|
+
resetIdentity?: Value;
|
|
28
|
+
};
|
|
29
|
+
/** Reactive engine-backed list: fixed browse ranges or SDK-journal search windows. Empty pages advance manually. */
|
|
30
|
+
export declare function usePaginatedList<Query extends ListQueryReference, Contract extends FailureContract = typeof queryErrors>(query: Query, args: Omit<FunctionArgs<Query>, 'paginationOpts'> | 'skip', options: PaginatedListOptions<Contract>): PaginatedList<Query, Contract>;
|
|
31
|
+
//#endregion
|
|
32
|
+
//# sourceMappingURL=use-paginated-list.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{queryErrors as e}from"../modules/contracts/errors.mjs";import{decodeFailure as t}from"./failures.mjs";import{initialPagination as n,nextPagination as r,paginationView as i,reconcilePagination as a}from"./pagination-state.mjs";import{getFunctionName as o}from"convex/server";import{convexToJson as s}from"convex/values";import{useMemo as c,useState as l}from"react";import{useQueries as u}from"convex/react";let d=0;function f(f,p,m){let{pageSize:h}=m;if(!Number.isInteger(h)||h<1||h>100)throw Error(`pageSize must be an integer from 1 to 100`);let g=p===`skip`,_=g?{}:p,v=JSON.stringify([o(f),s(_),s(m.resetIdentity??null),h,g]),[y,b]=l(()=>n(v,++d,g)),x=y;x.identity!==v&&(x=n(v,++d,g));let S=c(()=>Object.fromEntries(Object.entries(x.intervals).map(([e,t])=>{let n={numItems:h,cursor:t.cursor,id:x.epoch};return t.endCursor!==void 0&&(n.endCursor=t.endCursor),[e,{query:f,args:{..._,paginationOpts:n}}]})),[x.intervals,x.epoch,v]),C=u(S);x=a(x,C);let w=m.errors??e,T=x.error?t(x.error,w):void 0,E=x.error?t(x.error,e):void 0,D=x.error&&T?._tag===`DeclaredFailure`&&T.code===`invalid-cursor`&&E?._tag===`DeclaredFailure`&&E.code===`invalid-cursor`;if(D&&m.onInvalidCursor!==`throw`&&!x.restarted&&(x=n(v,++d,g,!0),T=void 0),x!==y&&b(x),D&&m.onInvalidCursor===`throw`)throw x.error;let O=i(x),k=x.epoch;return{...O,rows:O.rows,completeness:O.completeness,failure:T,resetKey:`${v}:${+!!x.restarted}`,fetchNextPage:()=>b(e=>e.epoch===k&&e.identity===v?r(e):e)}}export{f as usePaginatedList};
|
|
2
|
+
//# sourceMappingURL=use-paginated-list.mjs.map
|