@clipboard-health/json-api-nestjs
Version:
TypeScript-friendly utilities for adhering to the JSON:API specification with NestJS.
94 lines (93 loc) • 3.84 kB
TypeScript
import { type GreaterThan, type Subtract } from "type-fest";
import { z } from "zod";
import { type JsonApiDocument, type Relationship, type Relationships } from "../types";
/**
* Recursively traverse the JSON:API document to build a list of all possible relationship paths up
* to the specified depth, which prevents stack overflow for circular relationships. Use the result
* in `include` queries
*
* @template MapT - A map of ApiType to Zod schemas.
* @template DocumentT - The JSON:API document.
* @template Depth - The maximum depth for recursive relationship traversal.
* @template Prefix - The prefix for nested relationship paths.
*/
export type RelationshipPaths<MapT extends Record<string, z.ZodTypeAny>, DocumentT extends JsonApiDocument, Depth extends number = 5, Prefix extends string = ""> = GreaterThan<Depth, 0> extends true ? DocumentT["data"] extends Array<infer Data> | infer Data ? Data extends {
relationships?: infer Relation;
} ? Relation extends Relationships ? {
[K in keyof Relation]: K extends string ? NonNullable<Relation[K]> extends Relationship ? NonNullable<Relation[K]>["data"] extends {
type?: infer RelationT;
} | Array<{
type?: infer RelationT;
}> ? RelationT extends keyof MapT ? `${Prefix}${K}` | RelationshipPaths<MapT, z.infer<MapT[RelationT]>, Subtract<Depth, 1>, `${Prefix}${K}.`> : never : never : never : never;
}[keyof Relation] : never : never : never : never;
/**
* Creates a Zod schema for JSON:API include parameters.
*
* @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/format/#fetching-includes JSON:API includes}
*/
export declare function includeQuery<const FieldT extends readonly string[]>(fields: FieldT): {
include: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodOptional<z.ZodArray<z.ZodString, "many">>, string[] | undefined, unknown>, string[] | undefined, unknown>, FieldT[number][] | undefined, unknown>;
};