import { type LucidModelLike } from './aggregate.js';
import type { FieldAliases } from './field_aliases.js';
import type { FilterFieldTypeInfo } from './generate_client.js';
import type { ColumnFilter } from './operators.js';
import type { ComputedFields, FilterConfig, FilterFieldKind, FullTextSearchConfig, SortItem, VectorSimilarityConfig } from './types.js';
/**
 * Thrown when a {@link defineFilter} declaration is itself invalid (a developer
 * error surfaced at wiring time, not a bad client request). Request-time
 * violations (a disallowed field under `throwOnInvalid`) still surface as
 * `InvalidColumnFilterError` from the runner — this is the one new typed error,
 * for the declaration boundary the NestJS `@Filterable` decorator guarded.
 */
export declare class FilterDefinitionError extends Error {
    constructor(message: string);
}
/** Column-level allow-list for a relation: `'*'` (any column) or a bare-name list. */
export type RelationColumns = string[] | '*';
/**
 * The colocated allow-list form: field name → its column kind, declaring both at once.
 *
 * The array form makes every non-string field appear twice (once in `filterable`, once in
 * `fieldTypes`); this states each field and its type in one place, and desugars to exactly that
 * pair. Fields whose kind carries no contract can still use `'string'` — it is the no-op kind.
 *
 * ```ts
 * defineFilter({ filterable: { advisorId: 'string', dayOfWeek: 'number', isRecurring: 'boolean' } })
 * ```
 */
export type FilterableMap = Record<string, FilterFieldKind>;
/** `filterable` as accepted on input: the classic list/`'*'`, or the colocated map. */
export type FilterableInput = RelationColumns | FilterableMap;
/**
 * The filterable/sortable declaration for one whitelisted relation, keyed by the
 * relation name on the owning model. Column names are **bare** (unprefixed) — the
 * request field is the dotted path `relation.column`. Nesting `relations` again
 * whitelists deeper paths (`relation.child.column`), bounded by `maxDepth`.
 */
export interface RelationSpec {
    /** Columns of the related model clients may filter on. Defaults to `'*'`. */
    filterable?: RelationColumns;
    /** Columns of the related model clients may sort on. Defaults to `filterable`. */
    sortable?: RelationColumns;
    /** Further whitelisted relations reachable from this one (one hop deeper). */
    relations?: Record<string, RelationSpec>;
    /**
     * Numeric child columns exposed to the to-many aggregate functions
     * (`$sum`/`$avg`/`$min`/`$max`) — e.g. `['views', 'total']` makes
     * `posts.$sum.views` and `posts.$max.total` filterable/sortable. `$count`
     * needs no column and is synthesised for every to-many relation regardless.
     *
     * Because Lucid does not reflect a column's SQL type, listing a column here is
     * how the developer asserts it is numeric (the aggregate allow-list). Only
     * meaningful when the owning {@link DefineFilterOptions.model} is set so the
     * relation's FK/pivot metadata can be discovered; ignored otherwise.
     */
    aggregates?: string[];
}
/**
 * Reads the current tenant id from the request context (the AdonisJS
 * `HttpContext`, or any object the caller passes). A `null`/`undefined` result
 * means "no tenant in context" — tenant scoping is then skipped (opt-in, exactly
 * like the NestJS `@TenantScoped` no-op when no tenant resolves).
 */
export type TenantResolver = (ctx: unknown) => string | number | null | undefined;
/** Tenant auto-scope: constrain `column` to the tenant id resolved from ctx. */
export interface TenantScopeSpec {
    /** The model column constrained to the current tenant id (e.g. `'tenantId'`). */
    column: string;
    /** Resolves the tenant id from the request ctx. Nullish → scope is skipped. */
    resolve: TenantResolver;
}
/**
 * The declarative filter definition — the idiomatic AdonisJS reimplementation of
 * everything the NestJS `@Filterable`/`@Relations`/`@TenantScoped` decorators
 * encoded, as a plain options object (no decorators, no metadata reflection).
 */
export interface DefineFilterOptions {
    /**
     * Columns clients may filter on. Three forms:
     * - `['a', 'b']` — a bare allow-list.
     * - `'*'` — any base column (use with care).
     * - `{ a: 'string', b: 'number' }` — the colocated form: allow-list AND {@link
     *   DefineFilterOptions.fieldTypes} in one place, so a non-string field is not written twice.
     */
    filterable: FilterableInput;
    /** Columns clients may sort on. Defaults to {@link DefineFilterOptions.filterable}. */
    sortable?: RelationColumns;
    /**
     * Columns the free-text `search` term scans with a portable ILIKE — the
     * default search path when {@link DefineFilterOptions.fullText} is not set.
     */
    searchable?: string[];
    /**
     * Per-field column value types. Declaring a field here does two things at once:
     *
     * 1. **Server-side validation.** A query-string filter value is always a string, and Postgres
     *    silently casts the benign cases (`day_of_week = '3'` works) — so the gap stays invisible
     *    until a client sends something uncastable (`is_recurring = 'xyz'`), which raises
     *    `invalid input syntax for type boolean` at the database and surfaces as a **500 driven by
     *    user input**. With a declared kind the value is coerced up front, and one that can't be
     *    coerced is treated exactly like a disallowed field: dropped, or a loud
     *    `InvalidColumnFilterError` (→ 400) under {@link DefineFilterOptions.throwOnInvalid}.
     * 2. **Type-aware client codegen.** `make:filter-client` reads the same declaration, so the
     *    emitted client narrows operators per field instead of being operator-permissive.
     *
     * One declaration, both ends. Undeclared fields keep the previous behaviour (no coercion), so
     * adding this to an existing spec is opt-in and backwards compatible.
     */
    fieldTypes?: Record<string, FilterFieldTypeInfo>;
    /**
     * Opt-in Postgres tsvector full-text search. When set, the request `search`
     * string routes through `websearch_to_tsquery`/`@@` (and optional `ts_rank`)
     * instead of the ILIKE `searchable` scan. Column(s) + language + rank.
     */
    fullText?: FullTextSearchConfig;
    /** Whitelisted relations and their nested filterable/sortable columns. */
    relations?: Record<string, RelationSpec>;
    /**
     * The owning Lucid model — enables to-many aggregate fields
     * (`$count`/`$sum`/`$avg`/`$min`/`$max`). Its relation metadata (hasMany /
     * manyToMany FK + pivot columns) is introspected at build time to synthesise
     * aggregate computed sources for the whitelisted {@link relations}. Optional;
     * without it, aggregate fields are simply not available (the feature degrades
     * gracefully) — every other feature works with or without it. Also supplies
     * the default {@link table} (the model's table name) used as the correlated
     * subquery's outer alias.
     */
    model?: LucidModelLike;
    /**
     * Maximum relation-path depth (number of relation hops; a base column is
     * depth 0, `posts.title` is depth 1, `posts.comments.body` is depth 2).
     * Defaults to the deepest declared relation nesting. An explicit smaller value
     * caps paths even when a deeper relation is declared.
     */
    maxDepth?: number;
    /** Client-alias → resolved-target field remapping (see {@link resolveFieldAlias}). */
    aliases?: FieldAliases;
    /**
     * Opt-in pgvector embedding-similarity ordering (distinct from text `search`):
     * declares the vector column (and metric / threshold / top-K) rows are ranked
     * by when a request carries a query embedding. Additive — a spec without this
     * is unchanged.
     */
    vectorSimilarity?: VectorSimilarityConfig;
    /**
     * Virtual/computed fields — alias → dev-declared SQL expression
     * ({@link ComputedFields}). Two source forms: a verbatim string
     * (`{ fullName: "first || ' ' || last" }`) or a function
     * (`{ postCount: ({ alias }) => \`(SELECT COUNT(*) FROM posts WHERE posts.author_id = ${alias}.id)\` }`)
     * for correlated subqueries. A declared alias becomes filterable and sortable
     * as if it were a real column; the client value stays parameterized and only
     * the dev expression is inlined. Function-form sources need {@link table}.
     */
    computed?: ComputedFields;
    /**
     * The root model's table name, surfaced to computed-field functions as the
     * outer alias (Lucid gives the main table no generated alias, so the table
     * name IS the alias). Required for function-form computed fields and to-many
     * aggregate fields whose correlated subqueries reference the outer row.
     */
    table?: string;
    /** Opt-in tenant auto-scope read from ctx. */
    tenant?: TenantScopeSpec;
    /**
     * Server-declared filters always applied (in real column terms, AND-combined
     * with the request filters). Not subject to the allow-list — they are trusted
     * server policy, never client input.
     */
    defaultFilters?: ColumnFilter[];
    /** Sort applied when the request supplies none (stable default ordering). */
    defaultSort?: SortItem[];
    /** Default page size when the request gives none. Default 25 (in the runner). */
    defaultSize?: number;
    /** Hard cap on page size. Default 100 (in the runner). */
    maxSize?: number;
    /** Throw `InvalidColumnFilterError` on a disallowed field instead of dropping it. */
    throwOnInvalid?: boolean;
}
/**
 * A resolved, reusable, frozen filter definition produced by {@link defineFilter}.
 * Build it once (module scope) and hand it to {@link applyFilterFromRequest} on
 * every request. The `isFilterable`/`isSortable` predicates are the allow-list
 * boundary — relation-path and depth aware — that the runner enforces.
 */
export interface FilterSpec {
    readonly filterable: RelationColumns;
    readonly sortable: RelationColumns;
    readonly searchable: readonly string[];
    /** Declared column value kinds — drives value coercion AND client codegen. */
    readonly fieldTypes: Readonly<Record<string, FilterFieldTypeInfo>> | undefined;
    readonly fullText: FullTextSearchConfig | undefined;
    readonly relations: Readonly<Record<string, RelationSpec>>;
    readonly maxDepth: number;
    readonly aliases: FieldAliases | undefined;
    readonly vectorSimilarity: VectorSimilarityConfig | undefined;
    /** Declared computed/virtual fields (alias → SQL source). */
    readonly computed: ComputedFields | undefined;
    /** Root table name surfaced to computed-field functions as the outer alias. */
    readonly table: string | undefined;
    readonly tenant: TenantScopeSpec | undefined;
    readonly defaultFilters: readonly ColumnFilter[];
    readonly defaultSort: readonly SortItem[];
    readonly defaultSize: number | undefined;
    readonly maxSize: number | undefined;
    readonly throwOnInvalid: boolean;
    /** Is this (possibly relation-dotted, alias-resolved) field filterable? */
    isFilterable(field: string): boolean;
    /** Is this (possibly relation-dotted, alias-resolved) field sortable? */
    isSortable(field: string): boolean;
}
/**
 * Build a reusable {@link FilterSpec} from a declarative {@link DefineFilterOptions}.
 *
 * This is the AdonisJS-idiomatic replacement for the NestJS decorator stack:
 * instead of `@Filterable`/`@Relations`/`@TenantScoped` metadata read by an
 * interceptor, the definition is an explicit, framework-free config object built
 * once and passed explicitly to {@link applyFilterFromRequest}. It captures the
 * same feature set the decorators encoded — filterable/sortable allow-listing,
 * a relation whitelist with a depth cap, field aliases, tenant scoping, and
 * default filters/sort.
 */
export declare function defineFilter(options: DefineFilterOptions): FilterSpec;
/**
 * Project a {@link FilterSpec} onto the per-call {@link FilterConfig} the runner's
 * {@link applyFilter}/{@link applyCursor} consume. The allow-lists become
 * predicates (so relation-path + depth rules survive), and `defaultSort` fields
 * are unioned into the sortable predicate so a server-declared default ordering
 * is never dropped by the client-facing sort allow-list.
 */
export declare function specToFilterConfig(spec: FilterSpec): FilterConfig;
//# sourceMappingURL=filter_spec.d.ts.map