UNPKG

@clipboard-health/json-api-nestjs

Version:

TypeScript-friendly utilities for adhering to the JSON:API specification with NestJS.

86 lines (85 loc) 3.33 kB
import { z } from "zod"; import { type Field } from "../types"; export type Filter = "eq" | "ne" | "gt" | "gte" | "lt" | "lte"; export type FilterTuple = readonly [Filter, ...Filter[]]; export interface FilterValue { filters: FilterTuple; schema: z.ZodTypeAny; } export type InternalFilterMap<FieldT extends Field = Field> = Record<FieldT, FilterValue>; export type FilterMap<FieldT extends Field = Field> = Partial<InternalFilterMap<FieldT>>; export type FilterSchema<MapT extends InternalFilterMap> = { [K in keyof MapT]: z.ZodOptional<z.ZodEffects<z.ZodOptional<z.ZodObject<Record<MapT[K]["filters"][number], z.ZodOptional<z.ZodEffects<z.ZodOptional<z.ZodArray<MapT[K]["schema"]>>>>>>>>>; }; /** * Creates a Zod schema for JSON:API filters. * * @example * <embedex source="packages/example-nestjs/examples/query.ts"> * * ```ts * import { booleanString } from "@clipboard-health/contract-core"; * import { * cursorPaginationQuery, * fieldsQuery, * type FilterMap, * filterQuery, * includeQuery, * sortQuery, * } from "@clipboard-health/json-api-nestjs"; * import { z } from "zod"; * * import { * type ArticleAttributeFields, * type UserAttributeFields, * type UserIncludeFields, * } from "../src/contract"; * * const articleFields = ["title"] as const satisfies readonly ArticleAttributeFields[]; * const userFields = ["age", "dateOfBirth"] as const satisfies readonly UserAttributeFields[]; * const userIncludeFields = [ * "articles", * "articles.comments", * ] as const satisfies readonly UserIncludeFields[]; * const userFilterMap = { * age: { * filters: ["eq", "gt"], * schema: z.coerce.number().int().positive().max(125), * }, * dateOfBirth: { * filters: ["gte"], * schema: z.coerce.date().min(new Date("1900-01-01")).max(new Date()), * }, * isActive: { * filters: ["eq"], * schema: booleanString, * }, * } as const satisfies FilterMap<UserAttributeFields>; * * /** * * Disclaimer: Just because JSON:API supports robust querying doesn’t mean your service should * * implement them as they may require database indexes, which have a cost. **Implement only access * * patterns required by clients.** * * * * The spec says that if clients provide fields the server doesn’t support, it **MUST** return 400 * * Bad Request, hence the `.strict()`. * *\/ * export const query = z * .object({ * ...cursorPaginationQuery(), * ...fieldsQuery({ article: articleFields, user: userFields }), * ...filterQuery(userFilterMap), * ...sortQuery(userFields), * ...includeQuery(userIncludeFields), * }) * .strict(); * ``` * * </embedex> * * @see {@link https://jsonapi.org/recommendations/#filtering JSON:API filtering} * @see {@link https://discuss.jsonapi.org/t/share-propose-a-filtering-strategy/257 JSON:API filtering strategy} */ export declare function filterQuery<const MapT extends InternalFilterMap>(parameters: Readonly<MapT>): { filter: z.ZodOptional<z.ZodObject<FilterSchema<MapT>, "strict", z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<FilterSchema<MapT>>, any> extends infer T ? { [k in keyof T]: T[k]; } : never, z.baseObjectInputType<FilterSchema<MapT>> extends infer T_1 ? { [k_1 in keyof T_1]: T_1[k_1]; } : never>>; };