UNPKG

@adonis-agora/filter

Version:

Server-side query filtering/sorting/pagination for AdonisJS — Spatie-style input, a Lucid adapter, and field allow-listing. Part of the Agora ecosystem.

107 lines 5.93 kB
import type { FilterSpec } from './filter_spec.js'; /** * Codegen for the typed client — the AdonisJS-idiomatic port of the NestJS * `nestjs-filter` codegen. Where the NestJS package ships a standalone * `reflect-metadata` bin that reads decorator metadata off entity classes and * emits `filterQuery: () => filterQueryTyped<Fields, TypeMap>()` members into a * generated `api.ts`, the Adonis port has no decorators: the filter declaration * is already a plain runtime {@link FilterSpec} produced by `defineFilter(...)`. * * So generation is a **pure function of the spec** — string in, string out, no * AST walk, no metadata reflection, no framework. The ace command * (`make:filter-client`) is the thin IO wrapper that loads the app's declared * specs and writes the emitted module to disk. Keeping the generator pure keeps * it trivially testable (snapshot/string assertions) and framework-free, exactly * like the rest of this core. * * The emitted module targets `@adonis-agora/filter-client`'s `filterQueryTyped<Fields, * FieldTypes>()` factory — the same client the NestJS codegen emitted against — * so the two ecosystems share one browser runtime. */ import type { FilterFieldKind } from './types.js'; export type { FilterFieldKind }; /** * Optional per-field type information. A {@link FilterSpec} carries the * allow-list but not column value types (Lucid models aren't reflected here), so * the caller supplies types to unlock the client's type-aware operator narrowing. * Without it the emitted client is still field-name-safe, just operator-permissive. */ export interface FilterFieldTypeInfo { /** Classified value kind. Ignored when {@link typeRef} or {@link enumValues} is set. */ kind?: FilterFieldKind; /** Enum literal members — emitted as a union (`"A" | "B"` or `1 | 2`). Wins over {@link kind}. */ enumValues?: readonly (string | number)[]; /** A named TS type (e.g. `'Role'`) — emitted verbatim. Wins over everything. */ typeRef?: string; /** Nullable column — appends `| null` to the emitted type. */ nullable?: boolean; } /** Options for {@link generateFilterClient}. */ export interface GenerateFilterClientOptions { /** * Base name for the emitted identifiers — e.g. `'people'` yields * `PeopleFilterFields`, `PeopleFilterFieldTypes`, `peopleFilterMeta` and * `peopleFilterQuery()`. Non-identifier characters are stripped. */ name: string; /** Per-field value types keyed by (dotted) field path. Unlocks operator narrowing. */ fieldTypes?: Record<string, FilterFieldTypeInfo>; /** Import specifier for `filterQueryTyped`. Default `'@adonis-agora/filter-client'`. */ clientModule?: string; /** Cap relation-path depth of the emitted field union. Default {@link FilterSpec.maxDepth}. */ maxDepth?: number; /** Emit the "generated — do not edit" banner. Default `true`. */ banner?: boolean; } /** Enumerate the filterable field paths a spec admits, capped at `maxDepth` relation hops. */ export declare function filterableFieldPaths(spec: FilterSpec, maxDepth?: number): string[]; /** Enumerate the sortable field paths a spec admits, capped at `maxDepth` relation hops. */ export declare function sortableFieldPaths(spec: FilterSpec, maxDepth?: number): string[]; /** * Generate a typed filter client module for one {@link FilterSpec} — a pure * string transform. The emitted module exports, for a spec named `people`: * * - `type PeopleFilterFields` — the union of filterable field paths (the security * boundary, as concrete string literals); * - `interface PeopleFilterFieldTypes` — the per-field value-type map (only when * `fieldTypes` is supplied), which drives the client's operator/value narrowing; * - `const peopleFilterMeta` — runtime metadata (filterable/sortable/searchable * fields, whitelisted relations, per-field kinds, and the default sort / page * size / max size + cursor keyset the runner paginates by); * - `function peopleFilterQuery()` — a `filterQueryTyped<Fields, FieldTypes>()` * factory returning a type-safe builder scoped to this spec. */ export declare function generateFilterClient(spec: FilterSpec, options: GenerateFilterClientOptions): string; /** * One entry in a {@link FilterClientManifest}: a declared spec plus the optional * type info / codegen overrides the emitter needs. This is what an app exports * from `config/filter.ts` (as `filters`) for the `make:filter-client` command to * consume — the specs are already `defineFilter(...)` results, so no reflection. */ export interface FilterClientEntry { /** The `defineFilter(...)` result to generate a client for. */ spec: FilterSpec; /** Per-field value types (see {@link GenerateFilterClientOptions.fieldTypes}). */ fieldTypes?: Record<string, FilterFieldTypeInfo>; /** Override the client import specifier for this entry. */ clientModule?: string; /** Override the relation-path depth cap for this entry. */ maxDepth?: number; } /** A map of client name → declared spec, keyed by the emitted client's base name. */ export type FilterClientManifest = Record<string, FilterClientEntry>; /** A single generated client: its name, its AdonisJS-conventional file name, and its code. */ export interface GeneratedFilterClient { name: string; /** `snake_case`d file name, e.g. `blog_post_filter_client.ts`. */ filename: string; code: string; } /** * Expand a whole {@link FilterClientManifest} into per-spec generated modules — * the pure core the `make:filter-client` ace command writes to disk. Kept pure * (manifest in, modules out) so the command stays a thin IO wrapper and the * expansion is itself testable without a running app. */ export declare function generateFilterClients(manifest: FilterClientManifest): GeneratedFilterClient[]; //# sourceMappingURL=generate_client.d.ts.map