import type { CursorPayload } from '@nestjs-crud/core/cursor'; import type { QueryComposer, WhereBuilder } from '@nestjs-crud/core/query'; import type { CrudRequestOptions, JoinResolver } from '@nestjs-crud/core'; import type { ParsedRequestParams, QuerySort } from '@nestjs-crud/request'; /** * @internal — subject to change without semver-major. * Applies WHERE + sort + pagination + field selection + soft-delete + eager joins to a Prisma arg object. * * **OWNS the SQLi invariant**: the dotted-path sort branch validates * `relation` + `column` against `joinResolver.getAllowedColumnsFor(relation)` * before any identifier reaches Prisma's orderBy. * * Spike landmines respected: * - L1: no `previewFeatures = ["relationJoins"]` forced * - L2: to-one relation soft-delete compiles to parent-level `where`, NEVER inside `include` * - L3: `include` does NOT auto-filter soft-deleted relations (consumer opt-in only) * * @since 2.0.0 */ export interface PrismaQueryComposerConfig { entityColumns: string[]; entityPrimaryColumns: string[]; entityHasDeleteColumn: boolean; softDeleteColumn: string | null; onBadRequest: (msg: string) => void; joinResolver: JoinResolver; whereBuilder: WhereBuilder>; relationFields: string[]; } export declare class PrismaQueryComposer implements QueryComposer { private readonly entityColumns; private readonly entityPrimaryColumns; private readonly entityHasDeleteColumn; private readonly softDeleteColumn; private readonly onBadRequest; private readonly joinResolver; private readonly whereBuilder; private readonly relationFields; constructor(config: PrismaQueryComposerConfig); applyToQuery(q: any, parsed: ParsedRequestParams, options: CrudRequestOptions): any; getTake(query: ParsedRequestParams, options: CrudRequestOptions['query']): number | null; getSkip(query: ParsedRequestParams, take: number): number | null; /** * Apply the cursor-mode field allowlist + ORDER BY (with PK tie-breaker) on * top of the already-composed Prisma arg-object. The allowlist check and the * ORDER BY assignment run on every call, including the first page — a * defaulted sort field reaches `orderBy` on the same path a client-supplied * field does. Only the keyset WHERE composition is gated on a decoded * cursor, since there is no prior page position to resume from. * * SQLi guard: validates `sort.field` via the same `entityColumns` allowlist * used by `compileSort`. * * Bypasses Prisma's built-in `cursor:` arg — that argument is single-column * unique-key only and cannot accept `(sortField, id)` tuple semantics. * * @since 2.2.0 */ applyCursor(out: any, decoded: CursorPayload | null, sort: QuerySort): any; /** * SQLi invariant: dotted-path sort fields MUST round-trip through * `joinResolver.getAllowedColumnsFor(relation)` before reaching Prisma's orderBy. * Single-segment fields assert against `entityColumns`. */ private compileSort; /** * Build a Prisma `select` object from parsed fields filtered by allowed columns. * Prisma's `select` and `include` are mutually exclusive at the same level. * If both select fields and include relations are requested, prefer * `select` and merge include relations as `{ [relation]: true }` entries. */ private getSelectObject; /** * Build a Prisma `include` object for eager/requested joins. * * L3: include does NOT auto-inject deletedAt filter (consumer opt-in only). * L2: to-one filtered include is NEVER emitted — consumer routes filters * to parent where via SCondition dotted-path (handled by WhereBuilder). * * // TODO: to-many filtered include support (future work) */ private getIncludeObject; }