/** * Public input contract for `@qrvey/query-builder`. * * This is the engine-agnostic shape every consumer writes and every compiler * depends on. Keep it engine-neutral — engine-specific behavior lives in the * compilers, not here. */ /** Supported query engines. */ type Engine = 'elasticsearch' | 'opensearch' | 'postgresql' | 'clickhouse'; /** Normalized field types. `date` covers both date and datetime. */ type FieldType = 'string' | 'number' | 'boolean' | 'date'; /** The source table (SQL) or index (ES/OS). Exactly one per query. */ type QuerySource = { /** Table name (SQL) or index name/pattern (ES/OS). */ name: string; /** * Optional namespace that qualifies {@link name} for SQL engines: * PostgreSQL/Redshift/Snowflake schema, or the ClickHouse database. * Rendered as `"schema"."name"`. Ignored by Elasticsearch/OpenSearch * (an index has no schema). */ schema?: string; }; /** * Nested access path *within* a field that holds a JSON/object value * (equivalent to data-persistence `relativePath`). Omit for scalar fields. * Example: `field: 'profile', path: ['address', 'city']`. Rendered per engine — * JSON operators for SQL, a dotted field name for Elasticsearch/OpenSearch. */ type FieldPath = string[]; /** A projected field. */ type FieldSelection = { field: string; type: FieldType; path?: FieldPath; }; /** Closed, universal set of filter operators. */ type FilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'contains' | 'notContains' | 'startsWith' | 'endsWith' | 'between' | 'exists' | 'isNull' | 'isNotNull'; /** A single field comparison. */ type FilterCondition = { field: string; type: FieldType; path?: FieldPath; operator: FilterOperator; value?: unknown; }; /** Filter tree — a leaf condition or a logical combinator. */ type FilterExpression = FilterCondition | { and: FilterExpression[]; } | { or: FilterExpression[]; } | { not: FilterExpression; }; /** Metric aggregation functions. */ type AggregationFunction = 'count' | 'sum' | 'avg' | 'min' | 'max'; /** * An aggregation. `field` is optional only for `count`: `count` without a field * means "count rows/documents" (SQL `COUNT(*)`, ES/OS total hits); `count` with * a field counts non-null values. Every other function requires a `field`. */ type AggregationDefinition = { function: AggregationFunction; field?: string; type?: FieldType; path?: FieldPath; }; /** Calendar interval for date grouping. */ type DateGranularity = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; /** A group-by field. `granularity`/`offset` apply to `date` fields. */ type GroupByField = { field: string; type: FieldType; path?: FieldPath; granularity?: DateGranularity; offset?: string; /** Per-group-level cap (top-N values for this level). */ limit?: number; }; /** A having condition references an aggregation, not a raw field. */ type HavingCondition = { aggregation: AggregationDefinition; operator: FilterOperator; value?: unknown; }; /** Having tree — mirrors {@link FilterExpression} but over aggregations. */ type HavingExpression = HavingCondition | { and: HavingExpression[]; } | { or: HavingExpression[]; } | { not: HavingExpression; }; type SortDirection = 'asc' | 'desc'; /** Order by a field or by an aggregation result. */ type OrderByDefinition = { field: string; type: FieldType; path?: FieldPath; direction: SortDirection; } | { aggregation: AggregationDefinition; direction: SortDirection; }; /** * The normalized query definition. One source; portable across all engines. * A compiler turns this (via a validated QueryPlan) into an engine query. */ type QueryInput = { source: QuerySource; select?: FieldSelection[]; filter?: FilterExpression; groupBy?: GroupByField[]; aggregations?: AggregationDefinition[]; having?: HavingExpression; orderBy?: OrderByDefinition[]; limit?: number; skip?: number; cursor?: unknown[]; }; /** A single column/field assignment for an `update` mutation. */ type SetClause = { field: string; type: FieldType; value: unknown; }; /** * A granular update action beyond a plain `set`. Portable across engines: * - `increment` adds `by` to a numeric field (SQL `col = col + n`; ES painless). * - `append` appends `value` to an array field (SQL `array_append`/`arrayPushBack`). * - `removeField` clears a field (SQL `col = NULL`; ES `_source.remove`). * * Action targets are top-level columns (no nested `path`). */ type UpdateAction = { op: 'set'; field: string; type: FieldType; value: unknown; } | { op: 'increment'; field: string; by: number; } | { op: 'append'; field: string; type: FieldType; value: unknown; } | { op: 'removeField'; field: string; }; /** * A portable write mutation over one source. Compiles to `INSERT`/`DELETE`/ * `UPDATE` (SQL) or bulk index / `_delete_by_query` / `_update_by_query` (ES/OS). * * Safety: a `delete`/`update` without a `filter` affects EVERY row/document. * That is rejected unless `allowUnfiltered: true` is set explicitly, to guard * against accidental full-table/index wipes. (`insert` has no filter.) */ type MutationInput = { operation: 'insert'; source: QuerySource; /** One or more rows/documents to insert. All must share the same keys. */ rows: Array>; /** * Turns the insert into an upsert: on conflict of these key columns, * the remaining columns are updated. PostgreSQL renders `ON CONFLICT`; * ClickHouse relies on table-level dedup (ReplacingMergeTree); ES/OS * derive the document `_id` from these keys so re-indexing overwrites. */ onConflict?: string[]; } | { operation: 'delete'; source: QuerySource; filter?: FilterExpression; allowUnfiltered?: boolean; } | { operation: 'update'; source: QuerySource; /** Plain `field = value` assignments (sugar for `{ op: 'set', … }`). */ set?: SetClause[]; /** Granular actions (increment/append/removeField). */ actions?: UpdateAction[]; filter?: FilterExpression; allowUnfiltered?: boolean; }; /** * Internal logical model (QueryPlan / AST). * * The plan separates the public input contract from engine compilers. It * represents query *intent*, not engine syntax. Compilers consume a validated * plan; they must not depend on the raw {@link QueryInput}. * * `path` on every node points back to the input location it came from, so * validation errors can pinpoint the exact input segment. */ declare abstract class PlanNode { readonly path: string; abstract readonly kind: string; constructor(path: string); } declare class FieldRefNode extends PlanNode { readonly field: string; readonly type: FieldType; /** * Nested access path *within* `field` when it holds a JSON/object value * (equivalent to data-persistence `relativePath`). `null`/empty = scalar * field. Rendered per engine: JSON operators (SQL) or a dotted field * name (ES/OS). */ readonly nestedPath: string[] | null; readonly kind = "field-ref"; constructor(path: string, field: string, type: FieldType, /** * Nested access path *within* `field` when it holds a JSON/object value * (equivalent to data-persistence `relativePath`). `null`/empty = scalar * field. Rendered per engine: JSON operators (SQL) or a dotted field * name (ES/OS). */ nestedPath?: string[] | null); /** * Dotted name including the nested path (`profile.address.city`), used for * SQL column aliases, ES/OS field names and output metadata so the field's * output name is consistent across engines. Plain `field` when scalar. */ qualifiedName(): string; } declare class SourceNode extends PlanNode { readonly name: string; readonly schema: string | null; readonly kind = "source"; constructor(path: string, name: string, schema?: string | null); } declare class ProjectionNode extends PlanNode { readonly fields: FieldRefNode[]; readonly kind = "projection"; constructor(path: string, fields: FieldRefNode[]); } declare abstract class FilterNode extends PlanNode { } declare class AggregationNode extends PlanNode { readonly fn: AggregationFunction; /** `null` only for `count` without a field (COUNT(*)). */ readonly field: FieldRefNode | null; /** Deterministic output name, generated once during plan creation. */ readonly outputName: string; readonly kind = "aggregation"; constructor(path: string, fn: AggregationFunction, /** `null` only for `count` without a field (COUNT(*)). */ field: FieldRefNode | null, /** Deterministic output name, generated once during plan creation. */ outputName: string); } declare class GroupFieldNode extends PlanNode { readonly field: FieldRefNode; readonly granularity: DateGranularity | null; readonly offset: string | null; readonly limit: number | null; /** Deterministic output name for this group level. */ readonly outputName: string; readonly kind = "group-field"; constructor(path: string, field: FieldRefNode, granularity: DateGranularity | null, offset: string | null, limit: number | null, /** Deterministic output name for this group level. */ outputName: string); } declare class GroupingNode extends PlanNode { readonly groups: GroupFieldNode[]; readonly kind = "grouping"; constructor(path: string, groups: GroupFieldNode[]); } declare abstract class HavingNode extends PlanNode { } type OrderTarget = { kind: 'field'; field: FieldRefNode; } | { kind: 'aggregation'; aggregation: AggregationNode; }; declare class OrderNode extends PlanNode { readonly target: OrderTarget; readonly direction: SortDirection; readonly kind = "order"; constructor(path: string, target: OrderTarget, direction: SortDirection); } declare class PaginationNode extends PlanNode { readonly skip: number | null; readonly cursor: unknown[] | null; readonly kind = "pagination"; constructor(path: string, skip: number | null, cursor: unknown[] | null); } declare class LimitNode extends PlanNode { readonly limit: number; readonly kind = "limit"; constructor(path: string, limit: number); } declare class QueryPlan extends PlanNode { readonly source: SourceNode; readonly projection: ProjectionNode | null; readonly filter: FilterNode | null; readonly grouping: GroupingNode | null; readonly aggregations: AggregationNode[]; readonly having: HavingNode | null; readonly ordering: OrderNode[]; readonly pagination: PaginationNode; readonly limits: LimitNode; readonly kind = "query-plan"; constructor(path: string, source: SourceNode, projection: ProjectionNode | null, filter: FilterNode | null, grouping: GroupingNode | null, aggregations: AggregationNode[], having: HavingNode | null, ordering: OrderNode[], pagination: PaginationNode, limits: LimitNode); } /** * Logical model for write mutations (insert/update/delete). * * Mirrors the read {@link QueryPlan} but for mutations. Reuses {@link FilterNode} * for the WHERE/query criteria so filters behave identically to reads. */ /** How an update assignment mutates its target field. */ type AssignmentOp = 'set' | 'increment' | 'append' | 'removeField'; /** A single update assignment (`set`/`increment`/`append`/`removeField`). */ declare class AssignmentNode extends PlanNode { readonly field: FieldRefNode; readonly op: AssignmentOp; /** Value to set/increment-by/append; `null` for `removeField`. */ readonly value: unknown; readonly kind = "assignment"; constructor(path: string, field: FieldRefNode, op: AssignmentOp, /** Value to set/increment-by/append; `null` for `removeField`. */ value: unknown); } /** A delete/update/insert over one source. */ declare class MutationPlan extends PlanNode { readonly operation: 'delete' | 'update' | 'insert'; readonly source: SourceNode; /** Assignments for `update`; empty otherwise. */ readonly assignments: AssignmentNode[]; /** Criteria; `null` means "all rows/documents" (unfiltered / N/A). */ readonly filter: FilterNode | null; /** Ordered column names for `insert`; empty otherwise. */ readonly columns: string[]; /** Row values aligned to {@link columns} for `insert`; empty otherwise. */ readonly rows: unknown[][]; /** * Key columns for an upsert (`insert` with conflict resolution); `null` * for a plain insert or non-insert mutations. */ readonly conflictKeys: string[] | null; readonly kind = "mutation-plan"; constructor(path: string, operation: 'delete' | 'update' | 'insert', source: SourceNode, /** Assignments for `update`; empty otherwise. */ assignments: AssignmentNode[], /** Criteria; `null` means "all rows/documents" (unfiltered / N/A). */ filter: FilterNode | null, /** Ordered column names for `insert`; empty otherwise. */ columns?: string[], /** Row values aligned to {@link columns} for `insert`; empty otherwise. */ rows?: unknown[][], /** * Key columns for an upsert (`insert` with conflict resolution); `null` * for a plain insert or non-insert mutations. */ conflictKeys?: string[] | null); } /** * Compiler contract and output shapes. * * A compiler turns a validated {@link QueryPlan} into an engine-specific query * plus metadata. Engines are added by implementing a compiler (usually by * extending a family base + a dialect), never by changing this contract. */ type TranspileOptions = { engine?: Engine; }; /** Describes each field the query will return, for consumers to interpret rows. */ type QueryOutputField = { name: string; kind: 'field'; sourceField: string; type: FieldType; } | { name: string; kind: 'group'; sourceField: string; type: FieldType; granularity?: DateGranularity; } | { name: string; kind: 'aggregation'; sourceField: string; function: AggregationFunction; type: FieldType; }; type QueryOutputMetadata = { outputFields: QueryOutputField[]; pagination?: { mode: 'offset' | 'search_after'; requiresStableSort?: boolean; }; }; /** SQL family output (PostgreSQL, ClickHouse). */ type SqlTranspileResult = { engine: 'postgresql' | 'clickhouse'; kind: 'sql'; /** Full query with serialized values — debug/inspection. */ plainQuery: string; /** Recommended executable form. */ query: string; /** Positional bound values (e.g. PostgreSQL `$1, $2, …`). */ values: unknown[]; /** * Named bound values for engines that use named parameters (e.g. ClickHouse * `{pN:Type}` → `query_params`). Empty for positional dialects. */ namedValues: Record; metadata: QueryOutputMetadata; }; /** Search DSL family output (Elasticsearch, OpenSearch). */ type SearchDslTranspileResult = { engine: 'elasticsearch' | 'opensearch'; kind: 'search-dsl'; index: string; body: Record; metadata: QueryOutputMetadata; }; type TranspileResult = SqlTranspileResult | SearchDslTranspileResult; /** SQL family write mutation output (INSERT/DELETE/UPDATE). */ type SqlMutationResult = { engine: 'postgresql' | 'clickhouse'; kind: 'sql'; operation: 'delete' | 'update' | 'insert'; plainQuery: string; query: string; values: unknown[]; namedValues: Record; }; /** * Search DSL family write mutation output. For delete/update this carries a * `_delete_by_query`/`_update_by_query` `body`; for insert it carries the raw * `documents` for the executor to turn into a bulk index request. */ type SearchDslMutationResult = { engine: 'elasticsearch' | 'opensearch'; kind: 'search-dsl'; operation: 'delete' | 'update' | 'insert'; index: string; body: Record; documents?: Array>; /** Upsert key columns: the executor derives each document `_id` from these. */ onConflict?: string[]; }; type MutationTranspileResult = SqlMutationResult | SearchDslMutationResult; /** * Error model for `@qrvey/query-builder`. * * Every failure is a {@link QueryBuilderError} with a stable {@link * QueryBuilderErrorCode} and a `path` pointing at the offending input location * (e.g. `$.groupBy[0].limit`). Add a code here when a new failure mode appears. */ type QueryBuilderErrorCode = 'INVALID_INPUT' | 'MISSING_ENGINE' | 'UNSUPPORTED_ENGINE' | 'UNSUPPORTED_ENGINE_BEHAVIOR' | 'INVALID_IDENTIFIER' | 'INVALID_FIELD_TYPE' | 'INVALID_OPERATOR_FOR_TYPE' | 'INVALID_IN_VALUE' | 'INVALID_GROUP_SELECTION' | 'INVALID_AGGREGATION_SELECTION' | 'INVALID_HAVING_CONDITION' | 'INVALID_ORDER_BY' | 'INVALID_MUTATION' | 'UNFILTERED_MUTATION' | 'UNSUPPORTED_AGGREGATION_FOR_ENGINE' | 'UNSUPPORTED_DATE_GRANULARITY_FOR_ENGINE' | 'UNSUPPORTED_PAGINATION_FOR_ENGINE' | 'UNSUPPORTED_GROUP_LIMIT_FOR_ENGINE' | 'LIMIT_EXCEEDED' | 'SKIP_EXCEEDED' | 'MAX_SELECTED_FIELDS_EXCEEDED' | 'SEARCH_AFTER_REQUIRES_ORDER_BY' | 'AMBIGUOUS_PAGINATION'; type QueryBuilderErrorPayload = { code: QueryBuilderErrorCode; /** JSON-path-like pointer to the offending input, e.g. `$.select[0]`. */ path: string; message: string; details?: Record; }; declare class QueryBuilderError extends Error { readonly payload: QueryBuilderErrorPayload; readonly code: QueryBuilderErrorCode; readonly path: string; readonly details?: Record; constructor(payload: QueryBuilderErrorPayload); } type QueryBuilderOptions = { engine?: Engine; }; type ValidationResult = { valid: boolean; errors: QueryBuilderError[]; }; declare class QueryBuilder { private readonly input; private readonly options; constructor(input: QueryInput, options?: QueryBuilderOptions); /** Validate input + plan for the resolved engine without throwing. */ validate(options?: TranspileOptions): ValidationResult; /** Return the validated {@link QueryPlan}. Throws on the first error. */ plan(options?: TranspileOptions): QueryPlan; /** Compile the query for the resolved engine. Throws on validation errors. */ transpile(options?: TranspileOptions): TranspileResult; private assertValid; } /** * `MutationBuilder` — portable write mutations. Mirrors {@link QueryBuilder} but * compiles a {@link MutationInput} into an INSERT/UPSERT/UPDATE/DELETE (SQL) or * a bulk index / `_delete_by_query` / `_update_by_query` (ES/OS) via the engine * compiler's `compileMutation`. */ declare class MutationBuilder { private readonly input; private readonly options; constructor(input: MutationInput, options?: QueryBuilderOptions); /** Validate the mutation input without throwing. */ validate(): ValidationResult; /** Return the validated {@link MutationPlan}. Throws on the first error. */ plan(): MutationPlan; /** Compile the mutation for the resolved engine. Throws on validation errors. */ transpile(options?: TranspileOptions): MutationTranspileResult; } export { type AggregationDefinition, type AggregationFunction, type DateGranularity, type Engine, type FieldPath, type FieldSelection, type FieldType, type FilterCondition, type FilterExpression, type FilterOperator, type GroupByField, type HavingCondition, type HavingExpression, MutationBuilder, type MutationInput, MutationPlan, type MutationTranspileResult, type OrderByDefinition, QueryBuilder, QueryBuilderError, type QueryBuilderErrorCode, type QueryBuilderErrorPayload, type QueryBuilderOptions, type QueryInput, type QueryOutputField, type QueryOutputMetadata, QueryPlan, type QuerySource, type SearchDslMutationResult, type SearchDslTranspileResult, type SetClause, type SortDirection, type SqlMutationResult, type SqlTranspileResult, type TranspileOptions, type TranspileResult, type UpdateAction, type ValidationResult };