/** * Lazy-mode query builder. * * Companion to `Query` in `builder.ts`, but built for collections in lazy * mode where `snapshot()` is unavailable — records live in the adapter and * are pulled on demand. Dispatches through `PersistedCollectionIndex` to * resolve a candidate record-id set, then decrypts only those records. * * Scope: * - `.where(field, '==' | 'in', value)` — dispatched through the index * - `.where(field, other-op, value)` — evaluated against the decrypted * candidate set (non-indexed ops still require the field to be indexed * — we need SOMETHING to scope the candidate set) * - `.orderBy(field, dir?)` — dispatched through `orderedBy` when no * `==`/`in` clause is present; otherwise applied as an in-memory sort * over the candidate set * - `.limit(n)` / `.offset(n)` — page slice after filtering * - `.toArray()` / `.first()` / `.count()` — terminals * * Every field referenced by a where or orderBy clause MUST be indexed; * otherwise `toArray()` throws `IndexRequiredError`. This is deliberate: * silent scan-fallback would hide the very performance cliff that lazy-mode * indexes exist to prevent (see `docs/architecture.md` §indexes). */ import type { FieldClause, Operator } from '../../kernel/query/predicate.js'; import type { PersistedCollectionIndex } from './persisted-indexes.js'; import type { QueryField } from '../../kernel/types.js'; import type { ViaPipeline } from '../../kernel/via/pipeline.js'; export interface LazyOrderBy { readonly field: string; readonly direction: 'asc' | 'desc'; } /** * Source abstraction the LazyQuery runs against. Collection implements it. * Kept minimal so the builder stays test-friendly. */ export interface LazyQuerySource { readonly collectionName: string; readonly persistedIndexes: PersistedCollectionIndex; /** * Per-field bucket-key canonicalizer (#677 — lazy twin of the eager * `candidateRecords()` probe-resolution in `kernel/query/builder.ts`). * Consulted in `resolveCandidateIds()` for `==`/`in` clause values * BEFORE calling `lookupEqual`/`lookupIn`, so a mixed-era stored value * (money) probes the SAME canonical bucket the write path now * populates. A narrow closure — not the whole `ViaPipeline` — keeps * the with-lookup/kernel seam thin. `undefined` (no via, or no binding * covers the field) is the common case: falls back to the raw value, * unchanged from today. */ canonicalizeIndexKey?(field: string, value: unknown): string | undefined; /** Ensure `_idx//*` side-cars have been bulk-loaded into the mirror. */ ensurePersistedIndexesLoaded(): Promise; /** * Decrypt one record by id in RAW (pre-`present()`, stored-form) shape, * or return null if it's gone (#684 — the raw-fetch seam). Used by the * post-filter (`toArray()`) so a `clause.via.evaluate` (e.g. money) sees * the same operand/actual-value space as eager's `filterRecords` — the * DECODED form (e.g. money's canonical decimal) is only materialized * for survivors, via {@link decodeRecord}. */ getRawRecord(id: string): Promise; /** * Apply this collection's locale/virtual-field decode (`present()`) to a * RAW record already known to match — the output-side twin of * {@link getRawRecord}. Mirrors eager's `Query.decodeVia`. */ decodeRecord(record: unknown): Promise; /** * This collection's compiled Via pipeline (money, i18n, …), read lazily * (a method, not a captured value) so a late `_setVia` (#666) is honored * — same closure discipline as {@link canonicalizeIndexKey}. `undefined` * when the collection has no Via-covered fields. */ via(): ViaPipeline | undefined; } interface LazyPlan { readonly clauses: readonly FieldClause[]; readonly orderBy: readonly LazyOrderBy[]; readonly limit: number | undefined; readonly offset: number; } export declare class LazyQuery { private readonly source; private readonly plan; constructor(source: LazyQuerySource, plan?: LazyPlan); where(field: QueryField, op: Operator, value: V): LazyQuery; orderBy(field: QueryField, direction?: 'asc' | 'desc'): LazyQuery; limit(n: number): LazyQuery; offset(n: number): LazyQuery; toArray(): Promise; first(): Promise; count(): Promise; /** * Resolve the candidate record-id set to decrypt. Returns null when the * query has no usable driver — no `==`/`in` clause and no `orderBy` * clause that can scope the scan. Callers interpret null as * IndexRequiredError (see `toArray`). */ private resolveCandidateIds; } export {};