/** * This Source Code is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright (c) Infonomic Company Limited */ import type { DocumentFilter, FieldFilterOperator } from '../@types/db-types.js'; import type { CollectionDefinition } from '../@types/index.js'; import type { QueryPredicate, SortSpec } from '../@types/query-predicate.js'; import type { BylineLogger } from '../lib/logger.js'; type WhereClause = QueryPredicate; /** Where clause keys that map to document-level columns, not EAV stores. */ declare const DOCUMENT_LEVEL_KEYS: Set; /** * Optional context used to resolve cross-collection relation filters. * * When the `where` clause traverses a relation field with a nested * sub-clause (e.g. `{ category: { path: 'news' } }`), the parser needs * (a) the target collection's definition to resolve nested field types * and (b) the target collection's DB row id to emit the adapter-facing * `RelationFilter.targetCollectionId`. * * When `ctx` is omitted, nested relation sub-clauses are silently * skipped — bare-value or operator-object values on relation fields * still produce ordinary `$eq`-style filters against * `store_relation.target_document_id`. */ export interface ParseContext { /** All registered collection definitions. */ collections: readonly CollectionDefinition[]; /** Resolve a collection path → DB row id. */ resolveCollectionId: (path: string) => Promise; /** * Optional logger. When provided, dropped nested relation sub-clauses * (unknown target collection, misconfigured relation field) emit a * `debug` line so a regression is observable. Safe to omit. */ logger?: BylineLogger; } export interface ParsedWhere { /** Filter on document_versions.status (exact match). */ status?: string; /** Text search query (for collection-configured search fields). */ query?: string; /** * Filter on a document's `path` (resolved against `byline_document_paths` * via the locale priority chain) with an operator. */ pathFilter?: { operator: FieldFilterOperator; value: string; }; /** * Adapter-facing filter list: ordinary field filters and cross-collection * relation filters, intermixed. Consumed by * `IDocumentQueries.findDocuments({ filters })`. */ filters: DocumentFilter[]; } export interface ParsedSort { /** Field-level sort descriptor (when sorting by a collection field). */ fieldSort?: { fieldName: string; storeType: string; valueColumn: string; direction: 'asc' | 'desc'; }; /** Document-level sort column (when sorting by created_at, updated_at, path). */ orderBy?: string; orderDirection?: 'asc' | 'desc'; } declare const DOCUMENT_SORT_COLUMNS: Record; /** * Parse a client API `where` clause into document-level conditions and * adapter-facing DocumentFilter descriptors. When `ctx` is provided, * nested relation sub-clauses (e.g. `{ category: { path: 'news' } }`) * are resolved into `RelationFilter` entries; otherwise only * direct/operator predicates against the relation's own * `target_document_id` are emitted. * * Reserved-key rules inside a nested sub-clause: `status` and `path` * downshift to `DocumentColumnFilter` entries against the target * version's `document_versions` columns (the adapter wires these to * `td${depth}.status` / `td${depth}.path` via the inner relation scope); * `query` is dropped with a debug log because text search has no * sensible composition through a relation hop. */ export declare function parseWhere(where: WhereClause | undefined, definition: CollectionDefinition, ctx?: ParseContext): Promise; /** * Compile a predicate wholly to adapter `DocumentFilter`s. Unlike * `parseWhere`, top-level `status` and `path` are emitted as document-column * filters rather than scalar list-query options. This is the form used by * `beforeRead` on detail, populate, count, and tree reads. */ export declare function parsePredicateFilters(where: QueryPredicate | undefined, definition: CollectionDefinition, ctx?: ParseContext, options?: { strict?: boolean; }): Promise; /** * Combine a `beforeRead` hook predicate with a caller-supplied where * clause using implicit AND. Returns whichever side is non-empty, or * wraps both in `$and` when both are present. * * Defined at the predicate level (rather than merging two `ParsedWhere` * outputs) so the result still flows through `parseWhere` once — there * is one normalisation pass and one place where reserved keys, relation * lookups, and combinator flattening happen. `null` is treated the same * as `undefined` (the cache value `null` records "hook ran and applied * no scoping"). */ export declare function mergePredicates(hookPredicate: QueryPredicate | null | undefined, userWhere: WhereClause | undefined): WhereClause | undefined; /** * Parse a client API `sort` spec into either a field-level sort descriptor * or a document-level order column. */ export declare function parseSort(sort: SortSpec | undefined, definition: CollectionDefinition): ParsedSort; /** Exported for testing. */ export { DOCUMENT_LEVEL_KEYS, DOCUMENT_SORT_COLUMNS };