/** * Custom Filters Extension * * Provides the classifier, validators, and F helper namespace for the custom-filters surface. * * - `classifyFilters` — transforms a flat consumer `filters` object into the * ADR-0012 `OppFilters` request body (three-bucket: default → named top-level fields; * registered custom → `customFilters`; ad-hoc → `customFilters` passthrough), * throwing `FilterError` on the first invalid value. * - `validateRoutes` — registration-time validation; rejects unknown `filterType` * and custom names that collide with default-filter names. * - `validateFilterCall` — call-time validation; rejects operator/filterType mismatch * and value-shape mismatches for registered filters; shape-only check for ad-hoc. * - `F` — helper namespace that compiles `{operator, value}` raw filter objects. * * Request-body contract: ADR-0012 / OppFiltersSchema. * Core-field escape hatch: `gov.@` keys pass through as custom-filter * keys verbatim. * * @module @common-grants/sdk/extensions */ import { z } from "zod"; import { OppFiltersSchema } from "../schemas/zod/models"; import type { CustomFilterSpec, CustomFilterType, PluginRoutes } from "./types"; import { FilterError } from "./types"; /** * Maps each CustomFilterType to the Zod schema that validates its * `{operator, value}` pair. Each schema constrains both the allowed operator * enum and the value shape, so a single parse covers both checks. * Literal-typed (`as const`) so `CustomFilterInput` can project per-type inputs. */ export declare const FILTER_TYPE_SCHEMAS: { readonly stringComparison: z.ZodObject<{ operator: z.ZodUnion, z.ZodEnum<{ like: "like"; notLike: "notLike"; }>]>; value: z.ZodString; }, z.core.$strip>; readonly stringArray: z.ZodObject<{ operator: z.ZodEnum<{ in: "in"; notIn: "notIn"; }>; value: z.ZodArray; }, z.core.$strip>; readonly numberComparison: z.ZodObject<{ operator: z.ZodUnion, z.ZodEnum<{ eq: "eq"; neq: "neq"; }>]>; value: z.ZodNumber; }, z.core.$strip>; readonly numberArray: z.ZodObject<{ operator: z.ZodEnum<{ in: "in"; notIn: "notIn"; }>; value: z.ZodArray; }, z.core.$strip>; readonly numberRange: z.ZodObject<{ operator: z.ZodEnum<{ between: "between"; outside: "outside"; }>; value: z.ZodObject<{ min: z.ZodNumber; max: z.ZodNumber; }, z.core.$strip>; }, z.core.$strip>; readonly booleanComparison: z.ZodObject<{ operator: z.ZodEnum<{ eq: "eq"; neq: "neq"; }>; value: z.ZodBoolean; }, z.core.$strip>; readonly dateComparison: z.ZodObject<{ operator: z.ZodEnum<{ gt: "gt"; gte: "gte"; lt: "lt"; lte: "lte"; }>; value: z.ZodUnion; }, z.core.$strip>; readonly dateRange: z.ZodObject<{ operator: z.ZodEnum<{ between: "between"; outside: "outside"; }>; value: z.ZodObject<{ min: z.ZodUnion; max: z.ZodUnion; }, z.core.$strip>; }, z.core.$strip>; readonly moneyComparison: z.ZodObject<{ operator: z.ZodEnum<{ gt: "gt"; gte: "gte"; lt: "lt"; lte: "lte"; }>; value: z.ZodObject<{ amount: z.ZodString; currency: z.ZodString; }, z.core.$strip>; }, z.core.$strip>; readonly moneyRange: z.ZodObject<{ operator: z.ZodEnum<{ between: "between"; outside: "outside"; }>; value: z.ZodObject<{ min: z.ZodObject<{ amount: z.ZodString; currency: z.ZodString; }, z.core.$strip>; max: z.ZodObject<{ amount: z.ZodString; currency: z.ZodString; }, z.core.$strip>; }, z.core.$strip>; }, z.core.$strip>; }; /** Input type accepted for a registered custom filter of the given filterType. */ export type CustomFilterInput = z.input<(typeof FILTER_TYPE_SCHEMAS)[FT]>; /** * Helper namespace for building `{operator, value}` raw filter objects. * * Each helper compiles to the `DefaultFilter` wire shape accepted by ADR-0012. * Raw `{operator, value}` objects are also accepted by `classifyFilters` — F.* * is a convenience layer, not a requirement. * * NOTE: `F.in` uses the TS reserved word as an object key — valid as a property * key. The Python sibling uses `f.in_` to avoid the reserved-word restriction; * this cross-SDK naming difference is a documented divergence across SDKs. * * @example * ```typescript * const filter = F.eq("open"); * // → { operator: "eq", value: "open" } * * const range = F.between(100, 500); * // → { operator: "between", value: { min: 100, max: 500 } } * ``` */ export declare const F: { /** Equals — `{ operator: "eq", value }` */ eq: (value: V) => { operator: "eq"; value: V; }; /** Not equals — `{ operator: "neq", value }` */ neq: (value: V) => { operator: "neq"; value: V; }; /** Greater than — `{ operator: "gt", value }` */ gt: (value: V) => { operator: "gt"; value: V; }; /** Greater than or equal — `{ operator: "gte", value }` */ gte: (value: V) => { operator: "gte"; value: V; }; /** Less than — `{ operator: "lt", value }` */ lt: (value: V) => { operator: "lt"; value: V; }; /** Less than or equal — `{ operator: "lte", value }` */ lte: (value: V) => { operator: "lte"; value: V; }; /** Array inclusion — `{ operator: "in", value: [...] }` */ in: (value: V[]) => { operator: "in"; value: V[]; }; /** Array exclusion — `{ operator: "notIn", value: [...] }` */ notIn: (value: V[]) => { operator: "notIn"; value: V[]; }; /** String pattern match — `{ operator: "like", value }` */ like: (value: string) => { operator: "like"; value: string; }; /** String pattern non-match — `{ operator: "notLike", value }` */ notLike: (value: string) => { operator: "notLike"; value: string; }; /** Range (inclusive) — `{ operator: "between", value: { min, max } }` */ between: (min: V, max: V) => { operator: "between"; value: { min: V; max: V; }; }; /** Range (exclusive) — `{ operator: "outside", value: { min, max } }` */ outside: (min: V, max: V) => { operator: "outside"; value: { min: V; max: V; }; }; }; /** * Registration-time validation for a `PluginRoutes` declaration. * * Throws `FilterError` on: * 1. Unknown `filterType` (not one of the 11 `CustomFilterType` values) * 2. A custom filter name that collides with a default-filter field name * (`status`, `closeDateRange`, `totalFundingAvailableRange`, * `minAwardAmountRange`, `maxAwardAmountRange`) * 3. Filters declared on a route that does not support custom filters — a * `resource.method` not in `SUPPORTED_CUSTOM_FILTER_ROUTES` (e.g. * `opportunities.list`, whose core operation declares no `filters`) * * Duplicate filter names within a route-method need no check: filter names are * object keys, and JS object literals cannot represent duplicate keys. * * Implements ASVS L1 input validation at the plugin-author trust boundary. * * @param routes - The PluginRoutes declaration to validate * @throws {FilterError} on any constraint violation */ export declare function validateRoutes(routes: PluginRoutes): void; /** * Call-time validation for a single filter value against its registered spec. * * - For REGISTERED filters (spec provided): validates the `{operator, value}` * pair against the filterType's Zod schema — each schema constrains both the * allowed operator enum and the value shape, so one parse covers both checks. * - For AD-HOC filters (spec is undefined): the `{operator, value}` pair must be * well-formed for some known filterType (checked against `FILTER_TYPE_SCHEMAS`, * the same map registered filters use). The element type is not pinned to one * type, since ad-hoc filters carry no `filterType`. * * Fail-soft: returns a `FilterError` describing the problem, or `undefined` * when the value is valid. The caller (`classifyFilters`) throws returned * errors rather than aborting the whole call. * * @param spec - The registered `CustomFilterSpec` for this filter, or `undefined` for ad-hoc * @param filterName - The filter key (used in error `path`) * @param filterValue - The raw filter value from the consumer `filters` object * @returns A `FilterError` on operator/filterType mismatch or value-shape mismatch, else `undefined` */ export declare function validateFilterCall(spec: CustomFilterSpec | undefined, filterName: string, filterValue: unknown): FilterError | undefined; /** * Classifies a flat consumer `filters` object into the ADR-0012 `OppFilters` * request body, throwing on the first invalid value instead of dropping it. * * Three-bucket classification (defaults → top-level * fields; registered + ad-hoc → `customFilters`), but validation is fail-fast: * any invalid value — standard, registered, or ad-hoc — throws before a request * body is produced. Well-formed ad-hoc (unregistered) keys still pass through. * * @param routes - The `PluginRoutes` from the plugin definition * @param resourceKey - The resource name (e.g. `"opportunities"`) * @param methodKey - The method name (e.g. `"search"`) * @param consumerFilters - The flat consumer-facing filters object * @returns The classified `OppFilters` request body * @throws FilterError on the first invalid filter value */ export declare function classifyFilters(routes: PluginRoutes, resourceKey: string, methodKey: string, consumerFilters: Record): z.infer; //# sourceMappingURL=custom-filters.d.ts.map