@danceroutine/tango-resources 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { APIView, CreateAPIView, CreateModelMixin, DestroyModelMixin, GenericAPIView, ListAPIView, ListCreateAPIView, ListModelMixin, OffsetPaginationInput, OffsetPaginator, RetrieveAPIView, RetrieveDestroyAPIView, RetrieveModelMixin, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, UpdateModelMixin, __export, inferModelFieldParsers, view_exports } from "./view-CTogofVY.js";
1
+ import { APIView, CreateAPIView, CreateModelMixin, DestroyModelMixin, GenericAPIView, ListAPIView, ListCreateAPIView, ListModelMixin, OffsetPaginationInput, OffsetPaginator, RetrieveAPIView, RetrieveDestroyAPIView, RetrieveModelMixin, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, UpdateModelMixin, __export, inferModelFieldParsers, view_exports } from "./view-8PdNL5Ts.js";
2
2
  import { HttpErrorFactory, NotFoundError, TangoRequest, TangoResponse } from "@danceroutine/tango-core";
3
3
  import { z, z as z$1 } from "zod";
4
4
  import { Q } from "@danceroutine/tango-orm";
@@ -424,7 +424,7 @@ var CursorPaginator = class CursorPaginator {
424
424
  needsTotalCount() {
425
425
  return false;
426
426
  }
427
- toResponse(results) {
427
+ toResponse(results, _context) {
428
428
  const response = { results };
429
429
  if (this.nextCursor) response.next = this.buildPageLink(this.nextCursor);
430
430
  if (this.previousCursor) response.previous = this.buildPageLink(this.previousCursor);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["request: TangoRequest","user: TUser | null","params: Record<string, string>","value: unknown","request: Request","user?: TUser | null","key: string | symbol","value: T","spec: Record<string, FilterResolver<T>>","allowAllParams: boolean","config: FilterSetDefineConfig<T>","value: unknown","fieldDeclarations: Partial<Record<keyof T, FieldFilterDeclaration>>","fieldParsers: Partial<Record<keyof T, FilterValueParser>>","field: keyof T","declaration: FieldFilterDeclaration","parser: FilterValueParser | undefined","value: FieldFilterDeclaration","declaration: AliasFilterDeclaration<T>","value: AliasFilterDeclaration<T>","fields: readonly (keyof T)[]","lookup: FilterLookup","parser?: FilterValueParser","composed: Partial<Record<FilterKey<T>, FilterValue>>","value: ResolvedFilterValue | undefined","baseParam: string","value: string | string[] | undefined","value: ResolvedFilterValue","parsers: Partial<Record<keyof T, FilterValueParser>>","params: TangoQueryParams","filters: FilterInput<T>[]","param: string","resolver: FilterResolver<T>","filter: Partial<Record<FilterKey<T>, FilterValue>>","CursorPaginationInput: z.ZodType<CursorPaginationInputValue>","results: T[]","nextCursor: string | null","previousCursor: string | null","value: unknown","queryset: QuerySet<T>","perPage: number","cursorField: keyof T","params: TangoQueryParams","parsedDirection: CursorDirection","results: TResult[]","response: CursorPaginatedResponse<TResult>","_totalCount?: number","cursor?: string","page: number","cursor: string","item: T","payload: CursorPayload","parsed: unknown","input: unknown","record: unknown","input: unknown","id: TModel[keyof TModel]","data: SerializerCreateInput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>","_id: TModel[keyof TModel]","data: SerializerUpdateInput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>","config: ModelViewSetConfig<TModel, TSerializer>","viewsetOrConstructor: AnyModelViewSet | (new (...args: never[]) => AnyModelViewSet)","value: unknown","actions: T","name: string","explicitPath?: string","input: string","ctx: RequestContext","searchFilters: FilterInput<TModel>[]","_ctx: RequestContext","id: string","queryset: QuerySet<TModel>","error: unknown","action: ViewSetActionDescriptor","model: ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n }"],"sources":["../src/context/RequestContext.ts","../src/context/index.ts","../src/filters/internal/InternalFilterType.ts","../src/filters/FilterSet.ts","../src/filters/index.ts","../src/pagination/CursorPaginationInput.ts","../src/paginators/CursorPaginator.ts","../src/pagination/index.ts","../src/paginators/index.ts","../src/resource/index.ts","../src/serializer/Serializer.ts","../src/serializer/ModelSerializer.ts","../src/serializer/index.ts","../src/viewset/ModelViewSet.ts","../src/viewset/index.ts"],"sourcesContent":["import { TangoRequest } from '@danceroutine/tango-core';\n\n/**\n * Default user shape for RequestContext.\n * Consumers can provide their own user type via the TUser generic parameter.\n */\nexport interface BaseUser {\n id: string | number;\n roles?: string[];\n}\n\n/**\n * Normalized request context passed through the framework adapter into viewset methods.\n * Generic over the user type so consumers can plug in their own auth infrastructure.\n */\nexport class RequestContext<TUser = BaseUser> {\n static readonly BRAND = 'tango.resources.request_context' as const;\n readonly __tangoBrand: typeof RequestContext.BRAND = RequestContext.BRAND;\n private state: Map<string | symbol, unknown> = new Map();\n\n constructor(\n public readonly request: TangoRequest,\n public user: TUser | null = null,\n public params: Record<string, string> = {}\n ) {}\n\n /**\n * Narrow an unknown value to `RequestContext`.\n */\n static isRequestContext<TUser = BaseUser>(value: unknown): value is RequestContext<TUser> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === RequestContext.BRAND\n );\n }\n\n /**\n * Construct a context with optional user payload.\n */\n static create<TUser = BaseUser>(request: Request, user?: TUser | null): RequestContext<TUser> {\n return new RequestContext<TUser>(\n TangoRequest.isTangoRequest(request) ? request : new TangoRequest(request),\n user ?? null\n );\n }\n\n /**\n * Store arbitrary per-request state for downstream middleware/handlers.\n */\n setState<T>(key: string | symbol, value: T): void {\n this.state.set(key, value);\n }\n\n /**\n * Retrieve previously stored request state.\n */\n getState<T>(key: string | symbol): T | undefined {\n return this.state.get(key) as T | undefined;\n }\n\n /**\n * Check whether a state key has been set.\n */\n hasState(key: string | symbol): boolean {\n return this.state.has(key);\n }\n\n /**\n * Clone the context, including route params and request-local state.\n */\n clone(): RequestContext<TUser> {\n const cloned = new RequestContext<TUser>(this.request, this.user, { ...this.params });\n cloned.state = new Map(this.state);\n return cloned;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport type { BaseUser } from './RequestContext';\nexport { RequestContext } from './RequestContext';\n","export const InternalFilterType = {\n SCALAR: 'scalar',\n ILIKE: 'ilike',\n RANGE: 'range',\n IN: 'in',\n CUSTOM: 'custom',\n} as const;\n","import { TangoQueryParams } from '@danceroutine/tango-core';\nimport type { FilterInput, FilterKey, FilterValue, LookupType } from '@danceroutine/tango-orm';\nimport { InternalFilterType } from './internal/InternalFilterType';\nimport type { RangeOperator } from './RangeOperator';\n\n/**\n * Configuration for how a query parameter should be resolved into a filter.\n * Supports scalar equality, case-insensitive search, range comparisons, IN queries, and custom logic.\n */\nexport type FilterResolver<T> =\n | { type: typeof InternalFilterType.SCALAR; column: keyof T }\n | { type: typeof InternalFilterType.ILIKE; columns: (keyof T)[] }\n | { type: typeof InternalFilterType.RANGE; column: keyof T; op: RangeOperator }\n | { type: typeof InternalFilterType.IN; column: keyof T }\n | {\n type: typeof InternalFilterType.CUSTOM;\n apply: (value: string | string[] | undefined) => FilterInput<T> | undefined;\n };\n\nexport type FilterLookup = LookupType;\n\nexport type FilterValueParser = (raw: string | string[]) => FilterValue | FilterValue[] | undefined;\n\nexport type FieldFilterDeclaration =\n | true\n | readonly FilterLookup[]\n | {\n lookups?: readonly FilterLookup[];\n param?: string;\n parse?: FilterValueParser;\n };\n\nexport type AliasFilterDeclaration<T extends Record<string, unknown>> =\n | FilterResolver<T>\n | {\n field: keyof T;\n lookup?: FilterLookup;\n parse?: FilterValueParser;\n }\n | {\n fields: readonly (keyof T)[];\n lookup?: FilterLookup;\n parse?: FilterValueParser;\n };\n\nexport interface FilterSetDefineConfig<T extends Record<string, unknown>> {\n fields?: Partial<Record<keyof T, FieldFilterDeclaration>>;\n aliases?: Record<string, AliasFilterDeclaration<T>>;\n parsers?: Partial<Record<keyof T, FilterValueParser>>;\n all?: '__all__';\n}\n\ntype ResolvedFilterValue = string | string[] | FilterValue | FilterValue[];\n\n/**\n * Declarative query-param to filter translation.\n *\n * A `FilterSet` lets viewsets expose safe, explicit filtering behavior\n * without leaking raw ORM filter syntax to request handlers.\n */\nexport class FilterSet<T extends Record<string, unknown>> {\n static readonly BRAND = 'tango.resources.filter_set' as const;\n readonly __tangoBrand: typeof FilterSet.BRAND = FilterSet.BRAND;\n\n /**\n * Resolve matching query parameters into ORM filter inputs.\n */\n constructor(\n private readonly spec: Record<string, FilterResolver<T>>,\n private readonly allowAllParams: boolean = false\n ) {}\n\n /**\n * Build a filter set from Django-style field declarations.\n */\n static define<T extends Record<string, unknown>>(config: FilterSetDefineConfig<T>): FilterSet<T> {\n const spec = FilterSet.normalizeDefineConfig(config);\n return new FilterSet(spec, config.all === '__all__');\n }\n\n /**\n * Narrow an unknown value to `FilterSet`.\n */\n static isFilterSet<T extends Record<string, unknown>>(value: unknown): value is FilterSet<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === FilterSet.BRAND\n );\n }\n\n private static normalizeDefineConfig<T extends Record<string, unknown>>(\n config: FilterSetDefineConfig<T>\n ): Record<string, FilterResolver<T>> {\n const spec: Record<string, FilterResolver<T>> = {};\n const fieldDeclarations: Partial<Record<keyof T, FieldFilterDeclaration>> = config.fields ?? {};\n const fieldParsers: Partial<Record<keyof T, FilterValueParser>> = config.parsers ?? {};\n\n for (const rawField of Object.keys(fieldDeclarations) as Array<keyof T>) {\n const declaration = fieldDeclarations[rawField];\n if (declaration === undefined) continue;\n const parser = fieldParsers[rawField];\n FilterSet.addFieldDeclaration(spec, rawField, declaration, parser);\n }\n\n const aliases = config.aliases ?? {};\n for (const [param, declaration] of Object.entries(aliases)) {\n spec[param] = FilterSet.normalizeAliasDeclaration(declaration);\n }\n\n return spec;\n }\n\n private static addFieldDeclaration<T extends Record<string, unknown>>(\n spec: Record<string, FilterResolver<T>>,\n field: keyof T,\n declaration: FieldFilterDeclaration,\n parser: FilterValueParser | undefined\n ): void {\n if (declaration === true) {\n spec[String(field)] = FilterSet.createLookupResolver(field, 'exact', parser);\n return;\n }\n\n if (FilterSet.isLookupArray(declaration)) {\n for (const lookup of declaration) {\n const param = FilterSet.resolveLookupParam(String(field), lookup);\n spec[param] = FilterSet.createLookupResolver(field, lookup, parser);\n }\n return;\n }\n\n const lookups = declaration.lookups ?? ['exact'];\n const baseParam = declaration.param ?? String(field);\n const effectiveParser = declaration.parse ?? parser;\n\n for (const lookup of lookups) {\n const param = FilterSet.resolveLookupParam(baseParam, lookup);\n spec[param] = FilterSet.createLookupResolver(field, lookup, effectiveParser);\n }\n }\n\n private static isLookupArray(value: FieldFilterDeclaration): value is readonly FilterLookup[] {\n return Array.isArray(value);\n }\n\n private static normalizeAliasDeclaration<T extends Record<string, unknown>>(\n declaration: AliasFilterDeclaration<T>\n ): FilterResolver<T> {\n if (FilterSet.isFilterResolverDeclaration(declaration)) {\n return declaration;\n }\n\n if ('fields' in declaration) {\n const lookup = declaration.lookup ?? 'icontains';\n return FilterSet.createMultiFieldResolver(declaration.fields, lookup, declaration.parse);\n }\n\n return FilterSet.createLookupResolver(declaration.field, declaration.lookup ?? 'exact', declaration.parse);\n }\n\n private static isFilterResolverDeclaration<T extends Record<string, unknown>>(\n value: AliasFilterDeclaration<T>\n ): value is FilterResolver<T> {\n if (typeof value !== 'object' || value === null || !('type' in value)) {\n return false;\n }\n\n return [\n InternalFilterType.SCALAR,\n InternalFilterType.ILIKE,\n InternalFilterType.RANGE,\n InternalFilterType.IN,\n InternalFilterType.CUSTOM,\n ].includes(value.type);\n }\n\n private static createMultiFieldResolver<T extends Record<string, unknown>>(\n fields: readonly (keyof T)[],\n lookup: FilterLookup,\n parser?: FilterValueParser\n ): FilterResolver<T> {\n if (lookup === 'icontains' && parser === undefined) {\n return { type: InternalFilterType.ILIKE, columns: [...fields] };\n }\n\n return {\n type: InternalFilterType.CUSTOM,\n apply: (raw) => {\n const parsed = FilterSet.resolveParserValue(raw, parser);\n if (parsed === undefined) return undefined;\n\n const composed: Partial<Record<FilterKey<T>, FilterValue>> = {};\n for (const field of fields) {\n const segment = FilterSet.resolveLookupFilter(field, lookup, parsed);\n if (!segment) continue;\n Object.assign(composed, segment);\n }\n\n return Object.keys(composed).length > 0 ? (composed as FilterInput<T>) : undefined;\n },\n };\n }\n\n private static createLookupResolver<T extends Record<string, unknown>>(\n field: keyof T,\n lookup: FilterLookup,\n parser?: FilterValueParser\n ): FilterResolver<T> {\n if (parser !== undefined) {\n return {\n type: InternalFilterType.CUSTOM,\n apply: (raw) => {\n const parsed = FilterSet.resolveParserValue(raw, parser);\n if (parsed === undefined) return undefined;\n return FilterSet.resolveLookupFilter(field, lookup, parsed);\n },\n };\n }\n\n switch (lookup) {\n case 'exact':\n return { type: InternalFilterType.SCALAR, column: field };\n case 'in':\n return { type: InternalFilterType.IN, column: field };\n case 'lt':\n case 'lte':\n case 'gt':\n case 'gte':\n return { type: InternalFilterType.RANGE, column: field, op: lookup };\n case 'icontains':\n return { type: InternalFilterType.ILIKE, columns: [field] };\n default:\n return {\n type: InternalFilterType.CUSTOM,\n apply: (raw) => FilterSet.resolveLookupFilter(field, lookup, raw),\n };\n }\n }\n\n private static resolveLookupFilter<T extends Record<string, unknown>>(\n field: keyof T,\n lookup: FilterLookup,\n value: ResolvedFilterValue | undefined\n ): FilterInput<T> | undefined {\n if (value === undefined) return undefined;\n\n if (lookup === 'exact') {\n return { [field]: value } as FilterInput<T>;\n }\n\n if (lookup === 'in') {\n const arr = Array.isArray(value) ? value : String(value).split(',');\n const lookupKey = `${String(field)}__in` as FilterKey<T>;\n return { [lookupKey]: arr } as FilterInput<T>;\n }\n\n if (lookup === 'icontains') {\n const lookupKey = `${String(field)}__icontains` as FilterKey<T>;\n return { [lookupKey]: `%${FilterSet.toScalarString(value)}%` } as FilterInput<T>;\n }\n\n const lookupKey = `${String(field)}__${lookup}` as FilterKey<T>;\n return { [lookupKey]: value as FilterValue } as FilterInput<T>;\n }\n\n private static resolveLookupParam(baseParam: string, lookup: FilterLookup): string {\n if (lookup === 'exact') {\n return baseParam;\n }\n return `${baseParam}__${lookup}`;\n }\n\n private static resolveParserValue(\n value: string | string[] | undefined,\n parser?: FilterValueParser\n ): ResolvedFilterValue | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n if (parser === undefined) {\n return value;\n }\n\n return parser(value);\n }\n\n private static toScalarString(value: ResolvedFilterValue): string {\n if (Array.isArray(value)) {\n return value.join(',');\n }\n return String(value);\n }\n\n /**\n * Return a new filter set with parser-aware scalar/range/in resolvers for matching fields.\n */\n withFieldParsers(parsers: Partial<Record<keyof T, FilterValueParser>>): FilterSet<T> {\n if (Object.keys(parsers).length === 0) {\n return this;\n }\n\n let changed = false;\n const nextSpec = Object.fromEntries(\n Object.entries(this.spec).map(([param, resolver]) => {\n const nextResolver = this.applyFieldParserOverride(resolver, parsers);\n if (nextResolver !== resolver) {\n changed = true;\n }\n return [param, nextResolver];\n })\n ) as Record<string, FilterResolver<T>>;\n\n return changed ? new FilterSet(nextSpec, this.allowAllParams) : this;\n }\n\n /**\n * Apply all configured resolvers against query params.\n */\n apply(params: TangoQueryParams): FilterInput<T>[] {\n const filters: FilterInput<T>[] = [];\n const keys = new Set<string>();\n\n for (const [key] of params.entries()) {\n keys.add(key);\n }\n\n for (const key of keys) {\n const resolver = this.spec[key] ?? (this.allowAllParams ? this.buildAllResolver(key) : undefined);\n if (!resolver) continue;\n\n const rawValue = params.getAll(key);\n const value = rawValue.length > 1 ? rawValue : rawValue[0];\n\n if (value === undefined) continue;\n\n const filter = this.resolveFilter(resolver, value);\n if (filter) {\n filters.push(filter);\n }\n }\n\n return filters;\n }\n\n private buildAllResolver(param: string): FilterResolver<T> | undefined {\n const [rawField, ...rawLookupParts] = param.split('__');\n if (!rawField) {\n return undefined;\n }\n\n const field = rawField as keyof T;\n if (rawLookupParts.length === 0) {\n return { type: InternalFilterType.SCALAR, column: field };\n }\n\n const lookup = rawLookupParts.join('__') as FilterLookup;\n return FilterSet.createLookupResolver(field, lookup);\n }\n\n private resolveFilter(\n resolver: FilterResolver<T>,\n value: string | string[] | undefined\n ): FilterInput<T> | undefined {\n if (value === undefined) return undefined;\n\n switch (resolver.type) {\n case InternalFilterType.SCALAR:\n return { [resolver.column]: value } as FilterInput<T>;\n\n case InternalFilterType.ILIKE: {\n const pattern = `%${FilterSet.toScalarString(value)}%`;\n const filter: Partial<Record<FilterKey<T>, FilterValue>> = {};\n resolver.columns.forEach((col) => {\n filter[`${String(col)}__icontains` as FilterKey<T>] = pattern;\n });\n return filter;\n }\n\n case InternalFilterType.RANGE: {\n const lookupKey = `${String(resolver.column)}__${resolver.op}` as keyof FilterInput<T>;\n return { [lookupKey]: value } as FilterInput<T>;\n }\n\n case InternalFilterType.IN: {\n const arr = Array.isArray(value) ? value : String(value).split(',');\n const lookupKey = `${String(resolver.column)}__in` as keyof FilterInput<T>;\n return { [lookupKey]: arr } as FilterInput<T>;\n }\n\n case InternalFilterType.CUSTOM:\n return resolver.apply(value);\n\n default:\n return undefined;\n }\n }\n\n private applyFieldParserOverride(\n resolver: FilterResolver<T>,\n parsers: Partial<Record<keyof T, FilterValueParser>>\n ): FilterResolver<T> {\n switch (resolver.type) {\n case InternalFilterType.SCALAR: {\n const parser = parsers[resolver.column];\n return parser ? FilterSet.createLookupResolver(resolver.column, 'exact', parser) : resolver;\n }\n\n case InternalFilterType.RANGE: {\n const parser = parsers[resolver.column];\n return parser ? FilterSet.createLookupResolver(resolver.column, resolver.op, parser) : resolver;\n }\n\n case InternalFilterType.IN: {\n const parser = parsers[resolver.column];\n return parser ? FilterSet.createLookupResolver(resolver.column, 'in', parser) : resolver;\n }\n\n default:\n return resolver;\n }\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport type { FilterType } from './FilterType';\nexport type { RangeOperator } from './RangeOperator';\nexport type {\n AliasFilterDeclaration,\n FieldFilterDeclaration,\n FilterLookup,\n FilterResolver,\n FilterSetDefineConfig,\n FilterValueParser,\n} from './FilterSet';\nexport { FilterSet } from './FilterSet';\n","import { z } from 'zod';\n\nexport type CursorPaginationInputValue = {\n limit?: number;\n cursor: string | null;\n ordering?: string;\n};\n\nexport const CursorPaginationInput: z.ZodType<CursorPaginationInputValue> = z.object({\n limit: z.preprocess(\n (value) => {\n if (value === undefined || value === null || value === '') {\n return undefined;\n }\n const parsed = Number.parseInt(String(value), 10);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return undefined;\n }\n return parsed;\n },\n z\n .number()\n .int()\n .min(1)\n .transform((value) => Math.min(value, 100))\n .optional()\n ),\n cursor: z.string().nullable().default(null),\n ordering: z.string().optional(),\n});\n","import { TangoQueryParams } from '@danceroutine/tango-core';\nimport type { FilterInput, QuerySet } from '@danceroutine/tango-orm';\nimport type { Paginator, Page } from '../pagination/Paginator';\nimport type { CursorPaginatedResponse } from '../pagination/PaginatedResponse';\nimport { CursorPaginationInput } from '../pagination/CursorPaginationInput';\n\ntype CursorDirection = 'asc' | 'desc';\n\ntype CursorPayload = {\n v: 1;\n field: string;\n dir: CursorDirection;\n value: unknown;\n};\n\n/**\n * Represents a single cursor page of results.\n * Cursor pages do not expose numeric page navigation like offset pagination.\n */\nclass CursorPage<T> implements Page<T> {\n static readonly BRAND = 'tango.resources.cursor_page' as const;\n readonly __tangoBrand: typeof CursorPage.BRAND = CursorPage.BRAND;\n\n constructor(\n public readonly results: T[],\n public readonly nextCursor: string | null,\n public readonly previousCursor: string | null\n ) {}\n\n static isCursorPage<T>(value: unknown): value is CursorPage<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === CursorPage.BRAND\n );\n }\n\n /** Whether a next cursor token exists. */\n hasNext(): boolean {\n return this.nextCursor !== null;\n }\n\n /** Whether a previous cursor token exists. */\n hasPrevious(): boolean {\n return this.previousCursor !== null;\n }\n\n nextPageNumber(): number | null {\n return null;\n }\n\n previousPageNumber(): number | null {\n return null;\n }\n\n startIndex(): number {\n return 0;\n }\n\n endIndex(): number {\n return this.results.length;\n }\n}\n\n/**\n * Cursor-based paginator for stable forward navigation with opaque cursor tokens.\n * It supports `limit`, `cursor`, and `ordering` query params and returns DRF-style\n * paginated envelopes with cursor links.\n */\nexport class CursorPaginator<T extends Record<string, unknown>> implements Paginator<T, T, CursorPaginatedResponse<T>> {\n static readonly BRAND = 'tango.resources.cursor_paginator' as const;\n readonly __tangoBrand: typeof CursorPaginator.BRAND = CursorPaginator.BRAND;\n private limit: number;\n private cursor: string | null = null;\n private direction: CursorDirection = 'asc';\n private nextCursor: string | null = null;\n private previousCursor: string | null = null;\n\n constructor(\n private queryset: QuerySet<T>,\n private perPage: number = 25,\n private cursorField: keyof T = 'id' as keyof T\n ) {\n this.limit = perPage;\n }\n\n /**\n * Narrow an unknown value to `CursorPaginator`.\n */\n static isCursorPaginator<T extends Record<string, unknown>>(value: unknown): value is CursorPaginator<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === CursorPaginator.BRAND\n );\n }\n\n /**\n * Parse cursor pagination parameters from Tango query params.\n */\n parse(params: TangoQueryParams): void {\n const parsed = CursorPaginationInput.parse({\n limit: params.get('limit') ?? undefined,\n cursor: params.get('cursor'),\n ordering: params.get('ordering') ?? undefined,\n });\n\n this.limit = parsed.limit ?? this.perPage;\n this.cursor = parsed.cursor;\n\n const ordering = parsed.ordering;\n if (ordering) {\n const parsedDirection: CursorDirection = ordering.startsWith('-') ? 'desc' : 'asc';\n const parsedField = ordering.startsWith('-') ? ordering.slice(1) : ordering;\n this.direction = parsedField === String(this.cursorField) ? parsedDirection : 'asc';\n } else {\n this.direction = 'asc';\n }\n }\n\n /**\n * Parse params and return compatibility `{ limit, offset }` shape.\n */\n parseParams(params: TangoQueryParams): { limit: number; offset: number } {\n this.parse(params);\n return { limit: this.limit, offset: 0 };\n }\n\n /**\n * Build a paginated response payload with cursor links.\n */\n needsTotalCount(): boolean {\n return false;\n }\n\n toResponse<TResult>(results: TResult[]): CursorPaginatedResponse<TResult> {\n const response: CursorPaginatedResponse<TResult> = { results };\n if (this.nextCursor) {\n response.next = this.buildPageLink(this.nextCursor);\n }\n if (this.previousCursor) {\n response.previous = this.buildPageLink(this.previousCursor);\n }\n return response;\n }\n\n /**\n * Backward-compatible alias for `toResponse`.\n */\n getPaginatedResponse<TResult>(results: TResult[], _totalCount?: number): CursorPaginatedResponse<TResult> {\n return this.toResponse(results);\n }\n\n /**\n * Apply cursor constraints and ordering to a queryset.\n */\n apply(queryset: QuerySet<T>): QuerySet<T> {\n let qs = queryset.limit(this.limit + 1);\n if (this.cursor) {\n const decoded = this.decodeCursor(this.cursor);\n if (decoded.field !== String(this.cursorField)) {\n throw new Error('Invalid cursor: field mismatch');\n }\n const lookup = this.direction === 'asc' ? '__gt' : '__lt';\n const fieldLookup = `${String(this.cursorField)}${lookup}`;\n const filterInput = { [fieldLookup]: decoded.value } as FilterInput<T>;\n qs = qs.filter(filterInput);\n }\n const orderToken = this.direction === 'asc' ? String(this.cursorField) : `-${String(this.cursorField)}`;\n return qs.orderBy(orderToken as keyof T | `-${string}`);\n }\n\n /**\n * Fetch the next cursor page.\n */\n async paginate(cursor?: string): Promise<Page<T>> {\n const appliedCursor = cursor ?? this.cursor;\n this.cursor = appliedCursor;\n const fetched = await this.apply(this.queryset).fetch();\n const results = [...fetched.results];\n const hasMore = results.length > this.limit;\n\n if (hasMore) {\n results.pop();\n }\n\n this.previousCursor = appliedCursor ?? null;\n const last = results.at(-1);\n this.nextCursor = hasMore && last ? this.encodeCursor(last) : null;\n\n return new CursorPage(results, this.nextCursor, this.previousCursor);\n }\n\n /**\n * Cursor paginators only support page `1` as an entry point.\n */\n async getPage(page: number): Promise<Page<T>> {\n if (page !== 1) {\n throw new Error('CursorPaginator only supports getPage(1). Use cursor pagination for subsequent pages.');\n }\n return this.paginate();\n }\n\n private buildPageLink(cursor: string): string {\n const orderingToken = this.direction === 'asc' ? String(this.cursorField) : `-${String(this.cursorField)}`;\n return `?limit=${this.limit}&cursor=${encodeURIComponent(cursor)}&ordering=${encodeURIComponent(orderingToken)}`;\n }\n\n private encodeCursor(item: T): string {\n const payload: CursorPayload = {\n v: 1,\n field: String(this.cursorField),\n dir: this.direction,\n value: item[this.cursorField],\n };\n return Buffer.from(JSON.stringify(payload), 'utf-8').toString('base64');\n }\n\n private decodeCursor(cursor: string): CursorPayload {\n let parsed: unknown;\n try {\n parsed = JSON.parse(Buffer.from(cursor, 'base64').toString('utf-8'));\n } catch {\n throw new Error('Invalid cursor: malformed token');\n }\n\n if (\n !parsed ||\n typeof parsed !== 'object' ||\n (parsed as { v?: unknown }).v !== 1 ||\n typeof (parsed as { field?: unknown }).field !== 'string' ||\n ((parsed as { dir?: unknown }).dir !== 'asc' && (parsed as { dir?: unknown }).dir !== 'desc') ||\n !('value' in parsed)\n ) {\n throw new Error('Invalid cursor: unsupported payload');\n }\n\n return parsed as CursorPayload;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { OffsetPaginator } from '../paginators/OffsetPaginator';\nexport { CursorPaginator } from '../paginators/CursorPaginator';\nexport { OffsetPaginationInput } from './OffsetPaginationInput';\nexport type { OffsetPaginationInputValue } from './OffsetPaginationInput';\nexport { CursorPaginationInput } from './CursorPaginationInput';\nexport type { CursorPaginationInputValue } from './CursorPaginationInput';\nexport type { Paginator, Page } from './Paginator';\nexport type {\n BasePaginatedResponse,\n CursorPaginatedResponse,\n OffsetPaginatedResponse,\n PaginatedResponse,\n} from './PaginatedResponse';\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { CursorPaginator } from './CursorPaginator';\nexport { OffsetPaginator } from './OffsetPaginator';\n","/**\n * Domain boundary barrel: centralizes shared resource metadata contracts.\n */\n\nexport type { ResourceModelFieldMetadata, ResourceModelLike, ResourceModelMetadata } from './ResourceModelLike';\nexport type { GenericAPIViewOpenAPIDescription, ModelViewSetOpenAPIDescription } from './OpenAPIDescription';\n","import { z } from 'zod';\n\nexport type SerializerSchema = z.ZodTypeAny;\n\nexport type SerializerClass<\n TCreateSchema extends SerializerSchema = SerializerSchema,\n TUpdateSchema extends SerializerSchema = SerializerSchema,\n TOutputSchema extends SerializerSchema = SerializerSchema,\n> = {\n new (): Serializer<TCreateSchema, TUpdateSchema, TOutputSchema>;\n readonly createSchema: TCreateSchema;\n readonly updateSchema: TUpdateSchema;\n readonly outputSchema: TOutputSchema;\n};\n\nexport type AnySerializerClass = SerializerClass<SerializerSchema, SerializerSchema, SerializerSchema>;\n\nexport type SerializerCreateInput<TSerializer extends AnySerializerClass> = z.output<TSerializer['createSchema']>;\nexport type SerializerUpdateInput<TSerializer extends AnySerializerClass> = z.output<TSerializer['updateSchema']>;\nexport type SerializerOutput<TSerializer extends AnySerializerClass> = z.output<TSerializer['outputSchema']>;\n\n/**\n * DRF-inspired base serializer backed by Zod schemas.\n *\n * Tango serializers keep Zod as the source of truth for validation and type\n * inference while centralizing create, update, and representation workflows in\n * one class-owned contract.\n */\nexport abstract class Serializer<\n TCreateSchema extends SerializerSchema,\n TUpdateSchema extends SerializerSchema,\n TOutputSchema extends SerializerSchema,\n> {\n static readonly createSchema: SerializerSchema = z.unknown();\n static readonly updateSchema: SerializerSchema = z.unknown();\n static readonly outputSchema: SerializerSchema = z.unknown();\n\n /**\n * Return the serializer class for the current instance.\n */\n getSerializerClass(): SerializerClass<TCreateSchema, TUpdateSchema, TOutputSchema> {\n return this.constructor as SerializerClass<TCreateSchema, TUpdateSchema, TOutputSchema>;\n }\n\n /**\n * Return the Zod schema used for create payloads.\n */\n getCreateSchema(): TCreateSchema {\n return this.getSerializerClass().createSchema;\n }\n\n /**\n * Return the Zod schema used for update payloads.\n */\n getUpdateSchema(): TUpdateSchema {\n return this.getSerializerClass().updateSchema;\n }\n\n /**\n * Return the Zod schema used for serialized output.\n */\n getOutputSchema(): TOutputSchema {\n return this.getSerializerClass().outputSchema;\n }\n\n /**\n * Validate unknown input for create workflows.\n */\n deserializeCreate(input: unknown): z.output<TCreateSchema> {\n return this.getCreateSchema().parse(input);\n }\n\n /**\n * Validate unknown input for update workflows.\n */\n deserializeUpdate(input: unknown): z.output<TUpdateSchema> {\n return this.getUpdateSchema().parse(input);\n }\n\n /**\n * Convert a persisted record into its outward-facing representation.\n */\n toRepresentation(record: unknown): z.output<TOutputSchema> {\n return this.getOutputSchema().parse(record);\n }\n}\n","import type { ManagerLike } from '@danceroutine/tango-orm';\nimport type { ResourceModelLike } from '../resource/ResourceModelLike';\nimport {\n Serializer,\n type AnySerializerClass,\n type SerializerClass,\n type SerializerCreateInput,\n type SerializerOutput,\n type SerializerSchema,\n type SerializerUpdateInput,\n} from './Serializer';\n\nexport type ModelSerializerClass<\n TModel extends Record<string, unknown> = Record<string, unknown>,\n TCreateSchema extends SerializerSchema = SerializerSchema,\n TUpdateSchema extends SerializerSchema = SerializerSchema,\n TOutputSchema extends SerializerSchema = SerializerSchema,\n> = SerializerClass<TCreateSchema, TUpdateSchema, TOutputSchema> & {\n new (): ModelSerializer<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>;\n readonly model?: ResourceModelLike<TModel>;\n};\n\nexport type AnyModelSerializerClass = ModelSerializerClass<\n Record<string, unknown>,\n SerializerSchema,\n SerializerSchema,\n SerializerSchema\n>;\n\n/**\n * Zod-backed serializer with default model-manager persistence behavior.\n */\nexport abstract class ModelSerializer<\n TModel extends Record<string, unknown>,\n TCreateSchema extends SerializerSchema,\n TUpdateSchema extends SerializerSchema,\n TOutputSchema extends SerializerSchema,\n> extends Serializer<TCreateSchema, TUpdateSchema, TOutputSchema> {\n static readonly model?: unknown;\n\n /**\n * Return the Tango model backing this serializer.\n */\n getModel(): ResourceModelLike<TModel> {\n const model = (\n this.constructor as Partial<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>\n ).model;\n\n if (!model) {\n throw new Error(`${this.constructor.name} must define a static model or override getModel().`);\n }\n\n return model;\n }\n\n /**\n * Return the manager used for create and update workflows.\n */\n getManager(): ManagerLike<TModel> {\n return this.getModel().objects;\n }\n\n /**\n * Validate, enrich, persist, and serialize a create workflow.\n */\n async create(\n input: unknown\n ): Promise<SerializerOutput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>> {\n const validated = this.deserializeCreate(input);\n const prepared = await this.beforeCreate(validated);\n const created = await this.getManager().create(prepared);\n return this.toRepresentation(created);\n }\n\n /**\n * Validate, enrich, persist, and serialize an update workflow.\n */\n async update(\n id: TModel[keyof TModel],\n input: unknown\n ): Promise<SerializerOutput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>> {\n const validated = this.deserializeUpdate(input);\n const prepared = await this.beforeUpdate(id, validated);\n const updated = await this.getManager().update(id, prepared);\n return this.toRepresentation(updated);\n }\n\n /**\n * Override to normalize create input for this resource workflow before the\n * manager call.\n *\n * Model-owned persistence rules belong in model hooks so they also run for\n * scripts and direct manager usage.\n */\n protected async beforeCreate(\n data: SerializerCreateInput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>\n ): Promise<Partial<TModel>> {\n return data as Partial<TModel>;\n }\n\n /**\n * Override to normalize update input for this resource workflow before the\n * manager call.\n *\n * Model-owned persistence rules belong in model hooks so they also run for\n * scripts and direct manager usage.\n */\n protected async beforeUpdate(\n _id: TModel[keyof TModel],\n data: SerializerUpdateInput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>\n ): Promise<Partial<TModel>> {\n return data as Partial<TModel>;\n }\n}\n\nexport type { AnySerializerClass };\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport {\n Serializer,\n type SerializerClass,\n type AnySerializerClass,\n type SerializerCreateInput,\n type SerializerUpdateInput,\n type SerializerOutput,\n type SerializerSchema,\n} from './Serializer';\nexport { ModelSerializer, type ModelSerializerClass, type AnyModelSerializerClass } from './ModelSerializer';\n","import type { FilterInput, ManagerLike, QuerySet } from '@danceroutine/tango-orm';\nimport { HttpErrorFactory, TangoResponse, type JsonValue, NotFoundError } from '@danceroutine/tango-core';\nimport type { RequestContext } from '../context/index';\nimport type { FilterSet } from '../filters/index';\nimport { inferModelFieldParsers } from '../filters/inferModelFieldParsers';\nimport { OffsetPaginator } from '../paginators/OffsetPaginator';\nimport { Q } from '@danceroutine/tango-orm';\nimport type { Paginator } from '../pagination/index';\nimport type { ModelViewSetOpenAPIDescription } from '../resource/index';\nimport type { ResourceModelLike } from '../resource/index';\nimport type {\n AnyModelSerializerClass,\n ModelSerializerClass,\n SerializerOutput,\n SerializerSchema,\n} from '../serializer/index';\n\nexport type ViewSetActionScope = 'detail' | 'collection';\nexport type ViewSetActionMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\nexport interface ViewSetActionDescriptor {\n name: string;\n scope: ViewSetActionScope;\n methods: readonly ViewSetActionMethod[];\n path?: string;\n}\n\nexport interface ResolvedViewSetActionDescriptor extends ViewSetActionDescriptor {\n path: string;\n}\n\ntype AnyModelViewSet = ModelViewSet<Record<string, unknown>, AnyModelSerializerClass>;\n\n/**\n * Configuration for a ModelViewSet, defining how a serializer-backed model is exposed as an API resource.\n */\nexport interface ModelViewSetConfig<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> {\n /** Serializer class that owns validation, representation, and persistence hooks */\n serializer: TSerializer;\n\n /** Optional filter set defining which query parameters can filter the list endpoint */\n filters?: FilterSet<TModel>;\n\n /** Fields that clients are allowed to sort by via query parameters */\n orderingFields?: (keyof TModel)[];\n\n /** Fields that are searched when a free-text search query parameter is provided */\n searchFields?: (keyof TModel)[];\n\n /** Optional paginator factory used by list endpoints. */\n paginatorFactory?: (queryset: QuerySet<TModel>) => Paginator<TModel, SerializerOutput<TSerializer>>;\n}\n\n/**\n * Base class for creating RESTful API viewsets with built-in CRUD operations.\n * Provides list, retrieve, create, update, and delete methods with filtering,\n * search, pagination, and ordering support.\n */\nexport abstract class ModelViewSet<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> {\n static readonly BRAND = 'tango.resources.model_view_set' as const;\n static readonly actions: readonly ViewSetActionDescriptor[] = [];\n readonly __tangoBrand: typeof ModelViewSet.BRAND = ModelViewSet.BRAND;\n protected readonly serializerClass: TSerializer;\n protected readonly filters?: FilterSet<TModel>;\n protected readonly orderingFields: (keyof TModel)[];\n protected readonly searchFields: (keyof TModel)[];\n protected readonly paginatorFactory?: (\n queryset: QuerySet<TModel>\n ) => Paginator<TModel, SerializerOutput<TSerializer>>;\n private serializer?: InstanceType<TSerializer>;\n\n constructor(config: ModelViewSetConfig<TModel, TSerializer>) {\n this.serializerClass = config.serializer;\n this.filters = config.filters;\n this.orderingFields = config.orderingFields ?? [];\n this.searchFields = config.searchFields ?? [];\n this.paginatorFactory = config.paginatorFactory;\n }\n\n /**\n * Return the custom action descriptors declared by a viewset or constructor.\n */\n static getActions(\n viewsetOrConstructor: AnyModelViewSet | (new (...args: never[]) => AnyModelViewSet)\n ): readonly ResolvedViewSetActionDescriptor[] {\n const viewset = ModelViewSet.isModelViewSet(viewsetOrConstructor) ? viewsetOrConstructor : null;\n\n const constructorValue = viewset\n ? (viewset.constructor as { actions?: readonly ViewSetActionDescriptor[] })\n : (viewsetOrConstructor as { actions?: readonly ViewSetActionDescriptor[] });\n const actions = Array.isArray(constructorValue.actions) ? constructorValue.actions : [];\n\n return actions.map((action) => ({\n ...action,\n path: viewset\n ? viewset.resolveActionPath(action)\n : ModelViewSet.resolvePathFromDescriptor(action.name, action.path),\n }));\n }\n\n /**\n * Narrow an unknown value to `ModelViewSet`.\n */\n static isModelViewSet(value: unknown): value is ModelViewSet<Record<string, unknown>, AnyModelSerializerClass> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === ModelViewSet.BRAND\n );\n }\n\n /**\n * Preserve literal action inference while validating the descriptor shape.\n */\n static defineViewSetActions<const T extends readonly ViewSetActionDescriptor[]>(actions: T): T {\n return actions;\n }\n\n private static resolvePathFromDescriptor(name: string, explicitPath?: string): string {\n const normalized = (explicitPath?.trim() || ModelViewSet.toKebabCase(name)).replace(/^\\/+|\\/+$/g, '');\n if (!normalized) {\n throw new Error(`Invalid custom action path for '${name}'.`);\n }\n return normalized;\n }\n\n private static toKebabCase(input: string): string {\n return input\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/[_\\s]+/g, '-')\n .toLowerCase();\n }\n\n /**\n * Return the serializer class that owns this resource contract.\n */\n getSerializerClass(): TSerializer {\n return this.serializerClass;\n }\n\n /**\n * Return the serializer instance for the current resource.\n */\n getSerializer(): InstanceType<TSerializer> {\n if (!this.serializer) {\n this.serializer = new this.serializerClass() as InstanceType<TSerializer>;\n }\n\n return this.serializer;\n }\n\n /**\n * Describe the public HTTP contract that this resource contributes to OpenAPI generation.\n */\n describeOpenAPI(): ModelViewSetOpenAPIDescription<TModel, TSerializer> {\n const model = this.requireModelMetadata();\n return {\n model,\n outputSchema: this.getSerializer().getOutputSchema() as TSerializer['outputSchema'],\n createSchema: this.getSerializer().getCreateSchema() as TSerializer['createSchema'],\n updateSchema: this.getSerializer().getUpdateSchema() as TSerializer['updateSchema'],\n searchFields: this.searchFields,\n orderingFields: this.orderingFields,\n lookupField: this.getLookupFieldFromMetadata(model),\n lookupParam: 'id',\n allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n usesDefaultOffsetPagination: !this.paginatorFactory,\n actions: ModelViewSet.getActions(this as unknown as AnyModelViewSet),\n };\n }\n\n /**\n * List endpoint with filtering, search, ordering, and offset pagination.\n */\n async list(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const params = ctx.request.queryParams;\n const baseQueryset = this.getManager().query();\n const paginator = this.getPaginator(baseQueryset);\n paginator.parse(params);\n\n let qs = baseQueryset;\n\n if (this.filters) {\n const filterInputs = this.filters\n .withFieldParsers(inferModelFieldParsers(this.getSerializer().getModel()))\n .apply(params);\n if (filterInputs.length > 0) {\n qs = qs.filter(Q.and(...filterInputs));\n }\n }\n\n const search = params.getSearch();\n if (search && this.searchFields.length > 0) {\n const searchFilters: FilterInput<TModel>[] = this.searchFields.map((field) => {\n const lookup = `${String(field)}__icontains`;\n return { [lookup]: search } as FilterInput<TModel>;\n });\n qs = qs.filter(Q.or(...searchFilters));\n }\n\n const ordering = params.getOrdering();\n if (ordering.length > 0) {\n const orderTokens = ordering.filter((field) => {\n const cleanField = field.startsWith('-') ? field.slice(1) : field;\n return this.orderingFields.includes(cleanField as keyof TModel);\n });\n if (orderTokens.length > 0) {\n qs = qs.orderBy(...orderTokens.map((token) => token as keyof TModel | `-${string & keyof TModel}`));\n }\n }\n\n qs = paginator.apply(qs);\n const resultPromise = qs.fetch();\n const totalCountPromise = paginator.needsTotalCount()\n ? qs.count()\n : Promise.resolve<number | undefined>(undefined);\n const [result, totalCount] = await Promise.all([resultPromise, totalCountPromise]);\n const serializer = this.getSerializer();\n const response = paginator.toResponse(\n result.results.map((row) => serializer.toRepresentation(row)) as SerializerOutput<TSerializer>[],\n { totalCount }\n );\n\n return TangoResponse.json(response as unknown as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n /**\n * Retrieve endpoint for a single resource by id.\n */\n async retrieve(_ctx: RequestContext, id: string): Promise<TangoResponse> {\n try {\n const manager = this.getManager();\n const pk = manager.meta.pk;\n const filterById = { [pk]: id } as FilterInput<TModel>;\n const result = await manager.query().filter(filterById).fetchOne();\n\n if (!result) {\n throw new NotFoundError(`No ${manager.meta.table} record found for ${String(pk)}=${id}.`);\n }\n\n return TangoResponse.json(this.getSerializer().toRepresentation(result) as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n /**\n * Create endpoint: validate input, persist, and return serialized output.\n */\n async create(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const body = await ctx.request.json();\n const result = await this.getSerializer().create(body);\n\n return TangoResponse.created(undefined, result as JsonValue);\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n /**\n * Update endpoint: validate partial payload and persist by id.\n */\n async update(ctx: RequestContext, id: string): Promise<TangoResponse> {\n try {\n const body = await ctx.request.json();\n const pkValue = id as TModel[keyof TModel];\n const result = await this.getSerializer().update(pkValue, body);\n\n return TangoResponse.json(result as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n /**\n * Destroy endpoint: delete a resource by id.\n */\n async destroy(_ctx: RequestContext, id: string): Promise<TangoResponse> {\n try {\n const pkValue = id as TModel[keyof TModel];\n\n await this.getManager().delete(pkValue);\n\n return TangoResponse.noContent();\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected getPaginator(queryset: QuerySet<TModel>): Paginator<TModel, SerializerOutput<TSerializer>> {\n if (this.paginatorFactory) {\n return this.paginatorFactory(queryset);\n }\n return new OffsetPaginator<TModel>(queryset) as Paginator<TModel, SerializerOutput<TSerializer>>;\n }\n\n protected getManager(): ManagerLike<TModel> {\n return this.getSerializer().getManager();\n }\n\n /**\n * Convert thrown errors into normalized HTTP responses.\n */\n protected handleError(error: unknown): TangoResponse {\n const httpError = HttpErrorFactory.toHttpError(error);\n return TangoResponse.json(httpError.body as JsonValue, { status: httpError.status });\n }\n\n /**\n * Resolve route path segment(s) for a custom action.\n * Override this in subclasses to customize path derivation globally.\n */\n protected resolveActionPath(action: ViewSetActionDescriptor): string {\n return ModelViewSet.resolvePathFromDescriptor(action.name, action.path);\n }\n\n private requireModelMetadata(): ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n } {\n const model = this.getSerializer().getModel();\n\n if (!model.metadata) {\n throw new Error('OpenAPI generation requires Tango model metadata on ModelViewSet models.');\n }\n\n return model as ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n };\n }\n\n private getLookupFieldFromMetadata(\n model: ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n }\n ): keyof TModel {\n const primaryKeyField = model.metadata.fields.find((field) => field.primaryKey);\n\n if (!primaryKeyField) {\n throw new Error('OpenAPI generation requires a primary key field in Tango model metadata.');\n }\n\n return primaryKeyField.name as keyof TModel;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport {\n ModelViewSet,\n type ModelViewSetConfig,\n type ViewSetActionDescriptor,\n type ViewSetActionMethod,\n type ViewSetActionScope,\n type ResolvedViewSetActionDescriptor,\n} from './ModelViewSet';\nexport type { ModelViewSetOpenAPIDescription } from '../resource/index';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["request: TangoRequest","user: TUser | null","params: Record<string, string>","value: unknown","request: Request","user?: TUser | null","key: string | symbol","value: T","spec: Record<string, FilterResolver<T>>","allowAllParams: boolean","config: FilterSetDefineConfig<T>","value: unknown","fieldDeclarations: Partial<Record<keyof T, FieldFilterDeclaration>>","fieldParsers: Partial<Record<keyof T, FilterValueParser>>","field: keyof T","declaration: FieldFilterDeclaration","parser: FilterValueParser | undefined","value: FieldFilterDeclaration","declaration: AliasFilterDeclaration<T>","value: AliasFilterDeclaration<T>","fields: readonly (keyof T)[]","lookup: FilterLookup","parser?: FilterValueParser","composed: Partial<Record<FilterKey<T>, FilterValue>>","value: ResolvedFilterValue | undefined","baseParam: string","value: string | string[] | undefined","value: ResolvedFilterValue","parsers: Partial<Record<keyof T, FilterValueParser>>","params: TangoQueryParams","filters: FilterInput<T>[]","param: string","resolver: FilterResolver<T>","filter: Partial<Record<FilterKey<T>, FilterValue>>","CursorPaginationInput: z.ZodType<CursorPaginationInputValue>","results: T[]","nextCursor: string | null","previousCursor: string | null","value: unknown","queryset: QuerySet<T>","perPage: number","cursorField: keyof T","params: TangoQueryParams","parsedDirection: CursorDirection","results: TResult[]","_context?: { totalCount?: number; params?: TangoQueryParams }","response: CursorPaginatedResponse<TResult>","_totalCount?: number","queryset: QuerySet<T, TBaseResult, TSourceModel, THydrated>","cursor?: string","page: number","cursor: string","item: T","payload: CursorPayload","parsed: unknown","input: unknown","record: unknown","input: unknown","id: TModel[keyof TModel]","data: SerializerCreateInput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>","_id: TModel[keyof TModel]","data: SerializerUpdateInput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>","config: ModelViewSetConfig<TModel, TSerializer>","viewsetOrConstructor: AnyModelViewSet | (new (...args: never[]) => AnyModelViewSet)","value: unknown","actions: T","name: string","explicitPath?: string","input: string","ctx: RequestContext","searchFilters: FilterInput<TModel>[]","_ctx: RequestContext","id: string","queryset: QuerySet<TModel>","error: unknown","action: ViewSetActionDescriptor","model: ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n }"],"sources":["../src/context/RequestContext.ts","../src/context/index.ts","../src/filters/internal/InternalFilterType.ts","../src/filters/FilterSet.ts","../src/filters/index.ts","../src/pagination/CursorPaginationInput.ts","../src/paginators/CursorPaginator.ts","../src/pagination/index.ts","../src/paginators/index.ts","../src/resource/index.ts","../src/serializer/Serializer.ts","../src/serializer/ModelSerializer.ts","../src/serializer/index.ts","../src/viewset/ModelViewSet.ts","../src/viewset/index.ts"],"sourcesContent":["import { TangoRequest } from '@danceroutine/tango-core';\n\n/**\n * Default user shape for RequestContext.\n * Consumers can provide their own user type via the TUser generic parameter.\n */\nexport interface BaseUser {\n id: string | number;\n roles?: string[];\n}\n\n/**\n * Normalized request context passed through the framework adapter into viewset methods.\n * Generic over the user type so consumers can plug in their own auth infrastructure.\n */\nexport class RequestContext<TUser = BaseUser> {\n static readonly BRAND = 'tango.resources.request_context' as const;\n readonly __tangoBrand: typeof RequestContext.BRAND = RequestContext.BRAND;\n private state: Map<string | symbol, unknown> = new Map();\n\n constructor(\n public readonly request: TangoRequest,\n public user: TUser | null = null,\n public params: Record<string, string> = {}\n ) {}\n\n /**\n * Narrow an unknown value to `RequestContext`.\n */\n static isRequestContext<TUser = BaseUser>(value: unknown): value is RequestContext<TUser> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === RequestContext.BRAND\n );\n }\n\n /**\n * Construct a context with optional user payload.\n */\n static create<TUser = BaseUser>(request: Request, user?: TUser | null): RequestContext<TUser> {\n return new RequestContext<TUser>(\n TangoRequest.isTangoRequest(request) ? request : new TangoRequest(request),\n user ?? null\n );\n }\n\n /**\n * Store arbitrary per-request state for downstream middleware/handlers.\n */\n setState<T>(key: string | symbol, value: T): void {\n this.state.set(key, value);\n }\n\n /**\n * Retrieve previously stored request state.\n */\n getState<T>(key: string | symbol): T | undefined {\n return this.state.get(key) as T | undefined;\n }\n\n /**\n * Check whether a state key has been set.\n */\n hasState(key: string | symbol): boolean {\n return this.state.has(key);\n }\n\n /**\n * Clone the context, including route params and request-local state.\n */\n clone(): RequestContext<TUser> {\n const cloned = new RequestContext<TUser>(this.request, this.user, { ...this.params });\n cloned.state = new Map(this.state);\n return cloned;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport type { BaseUser } from './RequestContext';\nexport { RequestContext } from './RequestContext';\n","export const InternalFilterType = {\n SCALAR: 'scalar',\n ILIKE: 'ilike',\n RANGE: 'range',\n IN: 'in',\n CUSTOM: 'custom',\n} as const;\n","import { TangoQueryParams } from '@danceroutine/tango-core';\nimport type { FilterInput, FilterKey, FilterValue, LookupType } from '@danceroutine/tango-orm';\nimport { InternalFilterType } from './internal/InternalFilterType';\nimport type { RangeOperator } from './RangeOperator';\n\n/**\n * Configuration for how a query parameter should be resolved into a filter.\n * Supports scalar equality, case-insensitive search, range comparisons, IN queries, and custom logic.\n */\nexport type FilterResolver<T> =\n | { type: typeof InternalFilterType.SCALAR; column: keyof T }\n | { type: typeof InternalFilterType.ILIKE; columns: (keyof T)[] }\n | { type: typeof InternalFilterType.RANGE; column: keyof T; op: RangeOperator }\n | { type: typeof InternalFilterType.IN; column: keyof T }\n | {\n type: typeof InternalFilterType.CUSTOM;\n apply: (value: string | string[] | undefined) => FilterInput<T> | undefined;\n };\n\nexport type FilterLookup = LookupType;\n\nexport type FilterValueParser = (raw: string | string[]) => FilterValue | FilterValue[] | undefined;\n\nexport type FieldFilterDeclaration =\n | true\n | readonly FilterLookup[]\n | {\n lookups?: readonly FilterLookup[];\n param?: string;\n parse?: FilterValueParser;\n };\n\nexport type AliasFilterDeclaration<T extends Record<string, unknown>> =\n | FilterResolver<T>\n | {\n field: keyof T;\n lookup?: FilterLookup;\n parse?: FilterValueParser;\n }\n | {\n fields: readonly (keyof T)[];\n lookup?: FilterLookup;\n parse?: FilterValueParser;\n };\n\nexport interface FilterSetDefineConfig<T extends Record<string, unknown>> {\n fields?: Partial<Record<keyof T, FieldFilterDeclaration>>;\n aliases?: Record<string, AliasFilterDeclaration<T>>;\n parsers?: Partial<Record<keyof T, FilterValueParser>>;\n all?: '__all__';\n}\n\ntype ResolvedFilterValue = string | string[] | FilterValue | FilterValue[];\n\n/**\n * Declarative query-param to filter translation.\n *\n * A `FilterSet` lets viewsets expose safe, explicit filtering behavior\n * without leaking raw ORM filter syntax to request handlers.\n */\nexport class FilterSet<T extends Record<string, unknown>> {\n static readonly BRAND = 'tango.resources.filter_set' as const;\n readonly __tangoBrand: typeof FilterSet.BRAND = FilterSet.BRAND;\n\n /**\n * Resolve matching query parameters into ORM filter inputs.\n */\n constructor(\n private readonly spec: Record<string, FilterResolver<T>>,\n private readonly allowAllParams: boolean = false\n ) {}\n\n /**\n * Build a filter set from Django-style field declarations.\n */\n static define<T extends Record<string, unknown>>(config: FilterSetDefineConfig<T>): FilterSet<T> {\n const spec = FilterSet.normalizeDefineConfig(config);\n return new FilterSet(spec, config.all === '__all__');\n }\n\n /**\n * Narrow an unknown value to `FilterSet`.\n */\n static isFilterSet<T extends Record<string, unknown>>(value: unknown): value is FilterSet<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === FilterSet.BRAND\n );\n }\n\n private static normalizeDefineConfig<T extends Record<string, unknown>>(\n config: FilterSetDefineConfig<T>\n ): Record<string, FilterResolver<T>> {\n const spec: Record<string, FilterResolver<T>> = {};\n const fieldDeclarations: Partial<Record<keyof T, FieldFilterDeclaration>> = config.fields ?? {};\n const fieldParsers: Partial<Record<keyof T, FilterValueParser>> = config.parsers ?? {};\n\n for (const rawField of Object.keys(fieldDeclarations) as Array<keyof T>) {\n const declaration = fieldDeclarations[rawField];\n if (declaration === undefined) continue;\n const parser = fieldParsers[rawField];\n FilterSet.addFieldDeclaration(spec, rawField, declaration, parser);\n }\n\n const aliases = config.aliases ?? {};\n for (const [param, declaration] of Object.entries(aliases)) {\n spec[param] = FilterSet.normalizeAliasDeclaration(declaration);\n }\n\n return spec;\n }\n\n private static addFieldDeclaration<T extends Record<string, unknown>>(\n spec: Record<string, FilterResolver<T>>,\n field: keyof T,\n declaration: FieldFilterDeclaration,\n parser: FilterValueParser | undefined\n ): void {\n if (declaration === true) {\n spec[String(field)] = FilterSet.createLookupResolver(field, 'exact', parser);\n return;\n }\n\n if (FilterSet.isLookupArray(declaration)) {\n for (const lookup of declaration) {\n const param = FilterSet.resolveLookupParam(String(field), lookup);\n spec[param] = FilterSet.createLookupResolver(field, lookup, parser);\n }\n return;\n }\n\n const lookups = declaration.lookups ?? ['exact'];\n const baseParam = declaration.param ?? String(field);\n const effectiveParser = declaration.parse ?? parser;\n\n for (const lookup of lookups) {\n const param = FilterSet.resolveLookupParam(baseParam, lookup);\n spec[param] = FilterSet.createLookupResolver(field, lookup, effectiveParser);\n }\n }\n\n private static isLookupArray(value: FieldFilterDeclaration): value is readonly FilterLookup[] {\n return Array.isArray(value);\n }\n\n private static normalizeAliasDeclaration<T extends Record<string, unknown>>(\n declaration: AliasFilterDeclaration<T>\n ): FilterResolver<T> {\n if (FilterSet.isFilterResolverDeclaration(declaration)) {\n return declaration;\n }\n\n if ('fields' in declaration) {\n const lookup = declaration.lookup ?? 'icontains';\n return FilterSet.createMultiFieldResolver(declaration.fields, lookup, declaration.parse);\n }\n\n return FilterSet.createLookupResolver(declaration.field, declaration.lookup ?? 'exact', declaration.parse);\n }\n\n private static isFilterResolverDeclaration<T extends Record<string, unknown>>(\n value: AliasFilterDeclaration<T>\n ): value is FilterResolver<T> {\n if (typeof value !== 'object' || value === null || !('type' in value)) {\n return false;\n }\n\n return [\n InternalFilterType.SCALAR,\n InternalFilterType.ILIKE,\n InternalFilterType.RANGE,\n InternalFilterType.IN,\n InternalFilterType.CUSTOM,\n ].includes(value.type);\n }\n\n private static createMultiFieldResolver<T extends Record<string, unknown>>(\n fields: readonly (keyof T)[],\n lookup: FilterLookup,\n parser?: FilterValueParser\n ): FilterResolver<T> {\n if (lookup === 'icontains' && parser === undefined) {\n return { type: InternalFilterType.ILIKE, columns: [...fields] };\n }\n\n return {\n type: InternalFilterType.CUSTOM,\n apply: (raw) => {\n const parsed = FilterSet.resolveParserValue(raw, parser);\n if (parsed === undefined) return undefined;\n\n const composed: Partial<Record<FilterKey<T>, FilterValue>> = {};\n for (const field of fields) {\n const segment = FilterSet.resolveLookupFilter(field, lookup, parsed);\n if (!segment) continue;\n Object.assign(composed, segment);\n }\n\n return Object.keys(composed).length > 0 ? (composed as FilterInput<T>) : undefined;\n },\n };\n }\n\n private static createLookupResolver<T extends Record<string, unknown>>(\n field: keyof T,\n lookup: FilterLookup,\n parser?: FilterValueParser\n ): FilterResolver<T> {\n if (parser !== undefined) {\n return {\n type: InternalFilterType.CUSTOM,\n apply: (raw) => {\n const parsed = FilterSet.resolveParserValue(raw, parser);\n if (parsed === undefined) return undefined;\n return FilterSet.resolveLookupFilter(field, lookup, parsed);\n },\n };\n }\n\n switch (lookup) {\n case 'exact':\n return { type: InternalFilterType.SCALAR, column: field };\n case 'in':\n return { type: InternalFilterType.IN, column: field };\n case 'lt':\n case 'lte':\n case 'gt':\n case 'gte':\n return { type: InternalFilterType.RANGE, column: field, op: lookup };\n case 'icontains':\n return { type: InternalFilterType.ILIKE, columns: [field] };\n default:\n return {\n type: InternalFilterType.CUSTOM,\n apply: (raw) => FilterSet.resolveLookupFilter(field, lookup, raw),\n };\n }\n }\n\n private static resolveLookupFilter<T extends Record<string, unknown>>(\n field: keyof T,\n lookup: FilterLookup,\n value: ResolvedFilterValue | undefined\n ): FilterInput<T> | undefined {\n if (value === undefined) return undefined;\n\n if (lookup === 'exact') {\n return { [field]: value } as FilterInput<T>;\n }\n\n if (lookup === 'in') {\n const arr = Array.isArray(value) ? value : String(value).split(',');\n const lookupKey = `${String(field)}__in` as FilterKey<T>;\n return { [lookupKey]: arr } as FilterInput<T>;\n }\n\n if (lookup === 'icontains') {\n const lookupKey = `${String(field)}__icontains` as FilterKey<T>;\n return { [lookupKey]: `%${FilterSet.toScalarString(value)}%` } as FilterInput<T>;\n }\n\n const lookupKey = `${String(field)}__${lookup}` as FilterKey<T>;\n return { [lookupKey]: value as FilterValue } as FilterInput<T>;\n }\n\n private static resolveLookupParam(baseParam: string, lookup: FilterLookup): string {\n if (lookup === 'exact') {\n return baseParam;\n }\n return `${baseParam}__${lookup}`;\n }\n\n private static resolveParserValue(\n value: string | string[] | undefined,\n parser?: FilterValueParser\n ): ResolvedFilterValue | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n if (parser === undefined) {\n return value;\n }\n\n return parser(value);\n }\n\n private static toScalarString(value: ResolvedFilterValue): string {\n if (Array.isArray(value)) {\n return value.join(',');\n }\n return String(value);\n }\n\n /**\n * Return a new filter set with parser-aware scalar/range/in resolvers for matching fields.\n */\n withFieldParsers(parsers: Partial<Record<keyof T, FilterValueParser>>): FilterSet<T> {\n if (Object.keys(parsers).length === 0) {\n return this;\n }\n\n let changed = false;\n const nextSpec = Object.fromEntries(\n Object.entries(this.spec).map(([param, resolver]) => {\n const nextResolver = this.applyFieldParserOverride(resolver, parsers);\n if (nextResolver !== resolver) {\n changed = true;\n }\n return [param, nextResolver];\n })\n ) as Record<string, FilterResolver<T>>;\n\n return changed ? new FilterSet(nextSpec, this.allowAllParams) : this;\n }\n\n /**\n * Apply all configured resolvers against query params.\n */\n apply(params: TangoQueryParams): FilterInput<T>[] {\n const filters: FilterInput<T>[] = [];\n const keys = new Set<string>();\n\n for (const [key] of params.entries()) {\n keys.add(key);\n }\n\n for (const key of keys) {\n const resolver = this.spec[key] ?? (this.allowAllParams ? this.buildAllResolver(key) : undefined);\n if (!resolver) continue;\n\n const rawValue = params.getAll(key);\n const value = rawValue.length > 1 ? rawValue : rawValue[0];\n\n if (value === undefined) continue;\n\n const filter = this.resolveFilter(resolver, value);\n if (filter) {\n filters.push(filter);\n }\n }\n\n return filters;\n }\n\n private buildAllResolver(param: string): FilterResolver<T> | undefined {\n const [rawField, ...rawLookupParts] = param.split('__');\n if (!rawField) {\n return undefined;\n }\n\n const field = rawField as keyof T;\n if (rawLookupParts.length === 0) {\n return { type: InternalFilterType.SCALAR, column: field };\n }\n\n const lookup = rawLookupParts.join('__') as FilterLookup;\n return FilterSet.createLookupResolver(field, lookup);\n }\n\n private resolveFilter(\n resolver: FilterResolver<T>,\n value: string | string[] | undefined\n ): FilterInput<T> | undefined {\n if (value === undefined) return undefined;\n\n switch (resolver.type) {\n case InternalFilterType.SCALAR:\n return { [resolver.column]: value } as FilterInput<T>;\n\n case InternalFilterType.ILIKE: {\n const pattern = `%${FilterSet.toScalarString(value)}%`;\n const filter: Partial<Record<FilterKey<T>, FilterValue>> = {};\n resolver.columns.forEach((col) => {\n filter[`${String(col)}__icontains` as FilterKey<T>] = pattern;\n });\n return filter;\n }\n\n case InternalFilterType.RANGE: {\n const lookupKey = `${String(resolver.column)}__${resolver.op}` as keyof FilterInput<T>;\n return { [lookupKey]: value } as FilterInput<T>;\n }\n\n case InternalFilterType.IN: {\n const arr = Array.isArray(value) ? value : String(value).split(',');\n const lookupKey = `${String(resolver.column)}__in` as keyof FilterInput<T>;\n return { [lookupKey]: arr } as FilterInput<T>;\n }\n\n case InternalFilterType.CUSTOM:\n return resolver.apply(value);\n\n default:\n return undefined;\n }\n }\n\n private applyFieldParserOverride(\n resolver: FilterResolver<T>,\n parsers: Partial<Record<keyof T, FilterValueParser>>\n ): FilterResolver<T> {\n switch (resolver.type) {\n case InternalFilterType.SCALAR: {\n const parser = parsers[resolver.column];\n return parser ? FilterSet.createLookupResolver(resolver.column, 'exact', parser) : resolver;\n }\n\n case InternalFilterType.RANGE: {\n const parser = parsers[resolver.column];\n return parser ? FilterSet.createLookupResolver(resolver.column, resolver.op, parser) : resolver;\n }\n\n case InternalFilterType.IN: {\n const parser = parsers[resolver.column];\n return parser ? FilterSet.createLookupResolver(resolver.column, 'in', parser) : resolver;\n }\n\n default:\n return resolver;\n }\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport type { FilterType } from './FilterType';\nexport type { RangeOperator } from './RangeOperator';\nexport type {\n AliasFilterDeclaration,\n FieldFilterDeclaration,\n FilterLookup,\n FilterResolver,\n FilterSetDefineConfig,\n FilterValueParser,\n} from './FilterSet';\nexport { FilterSet } from './FilterSet';\n","import { z } from 'zod';\n\nexport type CursorPaginationInputValue = {\n limit?: number;\n cursor: string | null;\n ordering?: string;\n};\n\nexport const CursorPaginationInput: z.ZodType<CursorPaginationInputValue> = z.object({\n limit: z.preprocess(\n (value) => {\n if (value === undefined || value === null || value === '') {\n return undefined;\n }\n const parsed = Number.parseInt(String(value), 10);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return undefined;\n }\n return parsed;\n },\n z\n .number()\n .int()\n .min(1)\n .transform((value) => Math.min(value, 100))\n .optional()\n ),\n cursor: z.string().nullable().default(null),\n ordering: z.string().optional(),\n});\n","import { TangoQueryParams } from '@danceroutine/tango-core';\nimport type { FilterInput, QuerySet } from '@danceroutine/tango-orm';\nimport type { Paginator, Page } from '../pagination/Paginator';\nimport type { CursorPaginatedResponse } from '../pagination/PaginatedResponse';\nimport { CursorPaginationInput } from '../pagination/CursorPaginationInput';\n\ntype CursorDirection = 'asc' | 'desc';\n\ntype CursorPayload = {\n v: 1;\n field: string;\n dir: CursorDirection;\n value: unknown;\n};\n\n/**\n * Represents a single cursor page of results.\n * Cursor pages do not expose numeric page navigation like offset pagination.\n */\nclass CursorPage<T> implements Page<T> {\n static readonly BRAND = 'tango.resources.cursor_page' as const;\n readonly __tangoBrand: typeof CursorPage.BRAND = CursorPage.BRAND;\n\n constructor(\n public readonly results: T[],\n public readonly nextCursor: string | null,\n public readonly previousCursor: string | null\n ) {}\n\n static isCursorPage<T>(value: unknown): value is CursorPage<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === CursorPage.BRAND\n );\n }\n\n /** Whether a next cursor token exists. */\n hasNext(): boolean {\n return this.nextCursor !== null;\n }\n\n /** Whether a previous cursor token exists. */\n hasPrevious(): boolean {\n return this.previousCursor !== null;\n }\n\n nextPageNumber(): number | null {\n return null;\n }\n\n previousPageNumber(): number | null {\n return null;\n }\n\n startIndex(): number {\n return 0;\n }\n\n endIndex(): number {\n return this.results.length;\n }\n}\n\n/**\n * Cursor-based paginator for stable forward navigation with opaque cursor tokens.\n * It supports `limit`, `cursor`, and `ordering` query params and returns DRF-style\n * paginated envelopes with cursor links.\n */\nexport class CursorPaginator<T extends Record<string, unknown>> implements Paginator<T, T, CursorPaginatedResponse<T>> {\n static readonly BRAND = 'tango.resources.cursor_paginator' as const;\n readonly __tangoBrand: typeof CursorPaginator.BRAND = CursorPaginator.BRAND;\n private limit: number;\n private cursor: string | null = null;\n private direction: CursorDirection = 'asc';\n private nextCursor: string | null = null;\n private previousCursor: string | null = null;\n\n constructor(\n private queryset: QuerySet<T>,\n private perPage: number = 25,\n private cursorField: keyof T = 'id' as keyof T\n ) {\n this.limit = perPage;\n }\n\n /**\n * Narrow an unknown value to `CursorPaginator`.\n */\n static isCursorPaginator<T extends Record<string, unknown>>(value: unknown): value is CursorPaginator<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === CursorPaginator.BRAND\n );\n }\n\n /**\n * Parse cursor pagination parameters from Tango query params.\n */\n parse(params: TangoQueryParams): void {\n const parsed = CursorPaginationInput.parse({\n limit: params.get('limit') ?? undefined,\n cursor: params.get('cursor'),\n ordering: params.get('ordering') ?? undefined,\n });\n\n this.limit = parsed.limit ?? this.perPage;\n this.cursor = parsed.cursor;\n\n const ordering = parsed.ordering;\n if (ordering) {\n const parsedDirection: CursorDirection = ordering.startsWith('-') ? 'desc' : 'asc';\n const parsedField = ordering.startsWith('-') ? ordering.slice(1) : ordering;\n this.direction = parsedField === String(this.cursorField) ? parsedDirection : 'asc';\n } else {\n this.direction = 'asc';\n }\n }\n\n /**\n * Parse params and return compatibility `{ limit, offset }` shape.\n */\n parseParams(params: TangoQueryParams): { limit: number; offset: number } {\n this.parse(params);\n return { limit: this.limit, offset: 0 };\n }\n\n /**\n * Build a paginated response payload with cursor links.\n */\n needsTotalCount(): boolean {\n return false;\n }\n\n toResponse<TResult>(\n results: TResult[],\n _context?: { totalCount?: number; params?: TangoQueryParams }\n ): CursorPaginatedResponse<TResult> {\n const response: CursorPaginatedResponse<TResult> = { results };\n if (this.nextCursor) {\n response.next = this.buildPageLink(this.nextCursor);\n }\n if (this.previousCursor) {\n response.previous = this.buildPageLink(this.previousCursor);\n }\n return response;\n }\n\n /**\n * Backward-compatible alias for `toResponse`.\n */\n getPaginatedResponse<TResult>(results: TResult[], _totalCount?: number): CursorPaginatedResponse<TResult> {\n return this.toResponse(results);\n }\n\n /**\n * Apply cursor constraints and ordering to a queryset.\n */\n apply<TBaseResult extends Record<string, unknown>, TSourceModel, THydrated extends Record<string, unknown>>(\n queryset: QuerySet<T, TBaseResult, TSourceModel, THydrated>\n ): QuerySet<T, TBaseResult, TSourceModel, THydrated> {\n let qs = queryset.limit(this.limit + 1);\n if (this.cursor) {\n const decoded = this.decodeCursor(this.cursor);\n if (decoded.field !== String(this.cursorField)) {\n throw new Error('Invalid cursor: field mismatch');\n }\n const lookup = this.direction === 'asc' ? '__gt' : '__lt';\n const fieldLookup = `${String(this.cursorField)}${lookup}`;\n const filterInput = { [fieldLookup]: decoded.value } as FilterInput<T>;\n qs = qs.filter(filterInput);\n }\n const orderToken = this.direction === 'asc' ? String(this.cursorField) : `-${String(this.cursorField)}`;\n return qs.orderBy(orderToken as keyof T | `-${string}`);\n }\n\n /**\n * Fetch the next cursor page.\n */\n async paginate(cursor?: string): Promise<Page<T>> {\n const appliedCursor = cursor ?? this.cursor;\n this.cursor = appliedCursor;\n const fetched = await this.apply(this.queryset).fetch();\n const results = [...fetched.results];\n const hasMore = results.length > this.limit;\n\n if (hasMore) {\n results.pop();\n }\n\n this.previousCursor = appliedCursor ?? null;\n const last = results.at(-1);\n this.nextCursor = hasMore && last ? this.encodeCursor(last) : null;\n\n return new CursorPage(results, this.nextCursor, this.previousCursor);\n }\n\n /**\n * Cursor paginators only support page `1` as an entry point.\n */\n async getPage(page: number): Promise<Page<T>> {\n if (page !== 1) {\n throw new Error('CursorPaginator only supports getPage(1). Use cursor pagination for subsequent pages.');\n }\n return this.paginate();\n }\n\n private buildPageLink(cursor: string): string {\n const orderingToken = this.direction === 'asc' ? String(this.cursorField) : `-${String(this.cursorField)}`;\n return `?limit=${this.limit}&cursor=${encodeURIComponent(cursor)}&ordering=${encodeURIComponent(orderingToken)}`;\n }\n\n private encodeCursor(item: T): string {\n const payload: CursorPayload = {\n v: 1,\n field: String(this.cursorField),\n dir: this.direction,\n value: item[this.cursorField],\n };\n return Buffer.from(JSON.stringify(payload), 'utf-8').toString('base64');\n }\n\n private decodeCursor(cursor: string): CursorPayload {\n let parsed: unknown;\n try {\n parsed = JSON.parse(Buffer.from(cursor, 'base64').toString('utf-8'));\n } catch {\n throw new Error('Invalid cursor: malformed token');\n }\n\n if (\n !parsed ||\n typeof parsed !== 'object' ||\n (parsed as { v?: unknown }).v !== 1 ||\n typeof (parsed as { field?: unknown }).field !== 'string' ||\n ((parsed as { dir?: unknown }).dir !== 'asc' && (parsed as { dir?: unknown }).dir !== 'desc') ||\n !('value' in parsed)\n ) {\n throw new Error('Invalid cursor: unsupported payload');\n }\n\n return parsed as CursorPayload;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { OffsetPaginator } from '../paginators/OffsetPaginator';\nexport { CursorPaginator } from '../paginators/CursorPaginator';\nexport { OffsetPaginationInput } from './OffsetPaginationInput';\nexport type { OffsetPaginationInputValue } from './OffsetPaginationInput';\nexport { CursorPaginationInput } from './CursorPaginationInput';\nexport type { CursorPaginationInputValue } from './CursorPaginationInput';\nexport type { Paginator, Page } from './Paginator';\nexport type {\n BasePaginatedResponse,\n CursorPaginatedResponse,\n OffsetPaginatedResponse,\n PaginatedResponse,\n} from './PaginatedResponse';\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { CursorPaginator } from './CursorPaginator';\nexport { OffsetPaginator } from './OffsetPaginator';\n","/**\n * Domain boundary barrel: centralizes shared resource metadata contracts.\n */\n\nexport type { ResourceModelFieldMetadata, ResourceModelLike, ResourceModelMetadata } from './ResourceModelLike';\nexport type { GenericAPIViewOpenAPIDescription, ModelViewSetOpenAPIDescription } from './OpenAPIDescription';\n","import { z } from 'zod';\n\nexport type SerializerSchema = z.ZodTypeAny;\n\nexport type SerializerClass<\n TCreateSchema extends SerializerSchema = SerializerSchema,\n TUpdateSchema extends SerializerSchema = SerializerSchema,\n TOutputSchema extends SerializerSchema = SerializerSchema,\n> = {\n new (): Serializer<TCreateSchema, TUpdateSchema, TOutputSchema>;\n readonly createSchema: TCreateSchema;\n readonly updateSchema: TUpdateSchema;\n readonly outputSchema: TOutputSchema;\n};\n\nexport type AnySerializerClass = SerializerClass<SerializerSchema, SerializerSchema, SerializerSchema>;\n\nexport type SerializerCreateInput<TSerializer extends AnySerializerClass> = z.output<TSerializer['createSchema']>;\nexport type SerializerUpdateInput<TSerializer extends AnySerializerClass> = z.output<TSerializer['updateSchema']>;\nexport type SerializerOutput<TSerializer extends AnySerializerClass> = z.output<TSerializer['outputSchema']>;\n\n/**\n * DRF-inspired base serializer backed by Zod schemas.\n *\n * Tango serializers keep Zod as the source of truth for validation and type\n * inference while centralizing create, update, and representation workflows in\n * one class-owned contract.\n */\nexport abstract class Serializer<\n TCreateSchema extends SerializerSchema,\n TUpdateSchema extends SerializerSchema,\n TOutputSchema extends SerializerSchema,\n> {\n static readonly createSchema: SerializerSchema = z.unknown();\n static readonly updateSchema: SerializerSchema = z.unknown();\n static readonly outputSchema: SerializerSchema = z.unknown();\n\n /**\n * Return the serializer class for the current instance.\n */\n getSerializerClass(): SerializerClass<TCreateSchema, TUpdateSchema, TOutputSchema> {\n return this.constructor as SerializerClass<TCreateSchema, TUpdateSchema, TOutputSchema>;\n }\n\n /**\n * Return the Zod schema used for create payloads.\n */\n getCreateSchema(): TCreateSchema {\n return this.getSerializerClass().createSchema;\n }\n\n /**\n * Return the Zod schema used for update payloads.\n */\n getUpdateSchema(): TUpdateSchema {\n return this.getSerializerClass().updateSchema;\n }\n\n /**\n * Return the Zod schema used for serialized output.\n */\n getOutputSchema(): TOutputSchema {\n return this.getSerializerClass().outputSchema;\n }\n\n /**\n * Validate unknown input for create workflows.\n */\n deserializeCreate(input: unknown): z.output<TCreateSchema> {\n return this.getCreateSchema().parse(input);\n }\n\n /**\n * Validate unknown input for update workflows.\n */\n deserializeUpdate(input: unknown): z.output<TUpdateSchema> {\n return this.getUpdateSchema().parse(input);\n }\n\n /**\n * Convert a persisted record into its outward-facing representation.\n */\n toRepresentation(record: unknown): z.output<TOutputSchema> {\n return this.getOutputSchema().parse(record);\n }\n}\n","import type { ManagerLike } from '@danceroutine/tango-orm';\nimport type { ResourceModelLike } from '../resource/ResourceModelLike';\nimport {\n Serializer,\n type AnySerializerClass,\n type SerializerClass,\n type SerializerCreateInput,\n type SerializerOutput,\n type SerializerSchema,\n type SerializerUpdateInput,\n} from './Serializer';\n\nexport type ModelSerializerClass<\n TModel extends Record<string, unknown> = Record<string, unknown>,\n TCreateSchema extends SerializerSchema = SerializerSchema,\n TUpdateSchema extends SerializerSchema = SerializerSchema,\n TOutputSchema extends SerializerSchema = SerializerSchema,\n> = SerializerClass<TCreateSchema, TUpdateSchema, TOutputSchema> & {\n new (): ModelSerializer<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>;\n readonly model?: ResourceModelLike<TModel>;\n};\n\nexport type AnyModelSerializerClass = ModelSerializerClass<\n Record<string, unknown>,\n SerializerSchema,\n SerializerSchema,\n SerializerSchema\n>;\n\n/**\n * Zod-backed serializer with default model-manager persistence behavior.\n */\nexport abstract class ModelSerializer<\n TModel extends Record<string, unknown>,\n TCreateSchema extends SerializerSchema,\n TUpdateSchema extends SerializerSchema,\n TOutputSchema extends SerializerSchema,\n> extends Serializer<TCreateSchema, TUpdateSchema, TOutputSchema> {\n static readonly model?: unknown;\n\n /**\n * Return the Tango model backing this serializer.\n */\n getModel(): ResourceModelLike<TModel> {\n const model = (\n this.constructor as Partial<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>\n ).model;\n\n if (!model) {\n throw new Error(`${this.constructor.name} must define a static model or override getModel().`);\n }\n\n return model;\n }\n\n /**\n * Return the manager used for create and update workflows.\n */\n getManager(): ManagerLike<TModel> {\n return this.getModel().objects;\n }\n\n /**\n * Validate, enrich, persist, and serialize a create workflow.\n */\n async create(\n input: unknown\n ): Promise<SerializerOutput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>> {\n const validated = this.deserializeCreate(input);\n const prepared = await this.beforeCreate(validated);\n const created = await this.getManager().create(prepared);\n return this.toRepresentation(created);\n }\n\n /**\n * Validate, enrich, persist, and serialize an update workflow.\n */\n async update(\n id: TModel[keyof TModel],\n input: unknown\n ): Promise<SerializerOutput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>> {\n const validated = this.deserializeUpdate(input);\n const prepared = await this.beforeUpdate(id, validated);\n const updated = await this.getManager().update(id, prepared);\n return this.toRepresentation(updated);\n }\n\n /**\n * Override to normalize create input for this resource workflow before the\n * manager call.\n *\n * Model-owned persistence rules belong in model hooks so they also run for\n * scripts and direct manager usage.\n */\n protected async beforeCreate(\n data: SerializerCreateInput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>\n ): Promise<Partial<TModel>> {\n return data as Partial<TModel>;\n }\n\n /**\n * Override to normalize update input for this resource workflow before the\n * manager call.\n *\n * Model-owned persistence rules belong in model hooks so they also run for\n * scripts and direct manager usage.\n */\n protected async beforeUpdate(\n _id: TModel[keyof TModel],\n data: SerializerUpdateInput<ModelSerializerClass<TModel, TCreateSchema, TUpdateSchema, TOutputSchema>>\n ): Promise<Partial<TModel>> {\n return data as Partial<TModel>;\n }\n}\n\nexport type { AnySerializerClass };\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport {\n Serializer,\n type SerializerClass,\n type AnySerializerClass,\n type SerializerCreateInput,\n type SerializerUpdateInput,\n type SerializerOutput,\n type SerializerSchema,\n} from './Serializer';\nexport { ModelSerializer, type ModelSerializerClass, type AnyModelSerializerClass } from './ModelSerializer';\n","import type { FilterInput, ManagerLike, QuerySet } from '@danceroutine/tango-orm';\nimport { HttpErrorFactory, TangoResponse, type JsonValue, NotFoundError } from '@danceroutine/tango-core';\nimport type { RequestContext } from '../context/index';\nimport type { FilterSet } from '../filters/index';\nimport { inferModelFieldParsers } from '../filters/inferModelFieldParsers';\nimport { OffsetPaginator } from '../paginators/OffsetPaginator';\nimport { Q } from '@danceroutine/tango-orm';\nimport type { Paginator } from '../pagination/index';\nimport type { ModelViewSetOpenAPIDescription } from '../resource/index';\nimport type { ResourceModelLike } from '../resource/index';\nimport type {\n AnyModelSerializerClass,\n ModelSerializerClass,\n SerializerOutput,\n SerializerSchema,\n} from '../serializer/index';\n\nexport type ViewSetActionScope = 'detail' | 'collection';\nexport type ViewSetActionMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\nexport interface ViewSetActionDescriptor {\n name: string;\n scope: ViewSetActionScope;\n methods: readonly ViewSetActionMethod[];\n path?: string;\n}\n\nexport interface ResolvedViewSetActionDescriptor extends ViewSetActionDescriptor {\n path: string;\n}\n\ntype AnyModelViewSet = ModelViewSet<Record<string, unknown>, AnyModelSerializerClass>;\n\n/**\n * Configuration for a ModelViewSet, defining how a serializer-backed model is exposed as an API resource.\n */\nexport interface ModelViewSetConfig<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> {\n /** Serializer class that owns validation, representation, and persistence hooks */\n serializer: TSerializer;\n\n /** Optional filter set defining which query parameters can filter the list endpoint */\n filters?: FilterSet<TModel>;\n\n /** Fields that clients are allowed to sort by via query parameters */\n orderingFields?: (keyof TModel)[];\n\n /** Fields that are searched when a free-text search query parameter is provided */\n searchFields?: (keyof TModel)[];\n\n /** Optional paginator factory used by list endpoints. */\n paginatorFactory?: (queryset: QuerySet<TModel>) => Paginator<TModel, SerializerOutput<TSerializer>>;\n}\n\n/**\n * Base class for creating RESTful API viewsets with built-in CRUD operations.\n * Provides list, retrieve, create, update, and delete methods with filtering,\n * search, pagination, and ordering support.\n */\nexport abstract class ModelViewSet<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> {\n static readonly BRAND = 'tango.resources.model_view_set' as const;\n static readonly actions: readonly ViewSetActionDescriptor[] = [];\n readonly __tangoBrand: typeof ModelViewSet.BRAND = ModelViewSet.BRAND;\n protected readonly serializerClass: TSerializer;\n protected readonly filters?: FilterSet<TModel>;\n protected readonly orderingFields: (keyof TModel)[];\n protected readonly searchFields: (keyof TModel)[];\n protected readonly paginatorFactory?: (\n queryset: QuerySet<TModel>\n ) => Paginator<TModel, SerializerOutput<TSerializer>>;\n private serializer?: InstanceType<TSerializer>;\n\n constructor(config: ModelViewSetConfig<TModel, TSerializer>) {\n this.serializerClass = config.serializer;\n this.filters = config.filters;\n this.orderingFields = config.orderingFields ?? [];\n this.searchFields = config.searchFields ?? [];\n this.paginatorFactory = config.paginatorFactory;\n }\n\n /**\n * Return the custom action descriptors declared by a viewset or constructor.\n */\n static getActions(\n viewsetOrConstructor: AnyModelViewSet | (new (...args: never[]) => AnyModelViewSet)\n ): readonly ResolvedViewSetActionDescriptor[] {\n const viewset = ModelViewSet.isModelViewSet(viewsetOrConstructor) ? viewsetOrConstructor : null;\n\n const constructorValue = viewset\n ? (viewset.constructor as { actions?: readonly ViewSetActionDescriptor[] })\n : (viewsetOrConstructor as { actions?: readonly ViewSetActionDescriptor[] });\n const actions = Array.isArray(constructorValue.actions) ? constructorValue.actions : [];\n\n return actions.map((action) => ({\n ...action,\n path: viewset\n ? viewset.resolveActionPath(action)\n : ModelViewSet.resolvePathFromDescriptor(action.name, action.path),\n }));\n }\n\n /**\n * Narrow an unknown value to `ModelViewSet`.\n */\n static isModelViewSet(value: unknown): value is ModelViewSet<Record<string, unknown>, AnyModelSerializerClass> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === ModelViewSet.BRAND\n );\n }\n\n /**\n * Preserve literal action inference while validating the descriptor shape.\n */\n static defineViewSetActions<const T extends readonly ViewSetActionDescriptor[]>(actions: T): T {\n return actions;\n }\n\n private static resolvePathFromDescriptor(name: string, explicitPath?: string): string {\n const normalized = (explicitPath?.trim() || ModelViewSet.toKebabCase(name)).replace(/^\\/+|\\/+$/g, '');\n if (!normalized) {\n throw new Error(`Invalid custom action path for '${name}'.`);\n }\n return normalized;\n }\n\n private static toKebabCase(input: string): string {\n return input\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/[_\\s]+/g, '-')\n .toLowerCase();\n }\n\n /**\n * Return the serializer class that owns this resource contract.\n */\n getSerializerClass(): TSerializer {\n return this.serializerClass;\n }\n\n /**\n * Return the serializer instance for the current resource.\n */\n getSerializer(): InstanceType<TSerializer> {\n if (!this.serializer) {\n this.serializer = new this.serializerClass() as InstanceType<TSerializer>;\n }\n\n return this.serializer;\n }\n\n /**\n * Describe the public HTTP contract that this resource contributes to OpenAPI generation.\n */\n describeOpenAPI(): ModelViewSetOpenAPIDescription<TModel, TSerializer> {\n const model = this.requireModelMetadata();\n return {\n model,\n outputSchema: this.getSerializer().getOutputSchema() as TSerializer['outputSchema'],\n createSchema: this.getSerializer().getCreateSchema() as TSerializer['createSchema'],\n updateSchema: this.getSerializer().getUpdateSchema() as TSerializer['updateSchema'],\n searchFields: this.searchFields,\n orderingFields: this.orderingFields,\n lookupField: this.getLookupFieldFromMetadata(model),\n lookupParam: 'id',\n allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n usesDefaultOffsetPagination: !this.paginatorFactory,\n actions: ModelViewSet.getActions(this as unknown as AnyModelViewSet),\n };\n }\n\n /**\n * List endpoint with filtering, search, ordering, and offset pagination.\n */\n async list(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const params = ctx.request.queryParams;\n const baseQueryset = this.getManager().query();\n const paginator = this.getPaginator(baseQueryset);\n paginator.parse(params);\n\n let qs = baseQueryset;\n\n if (this.filters) {\n const filterInputs = this.filters\n .withFieldParsers(inferModelFieldParsers(this.getSerializer().getModel()))\n .apply(params);\n if (filterInputs.length > 0) {\n qs = qs.filter(Q.and(...filterInputs));\n }\n }\n\n const search = params.getSearch();\n if (search && this.searchFields.length > 0) {\n const searchFilters: FilterInput<TModel>[] = this.searchFields.map((field) => {\n const lookup = `${String(field)}__icontains`;\n return { [lookup]: search } as FilterInput<TModel>;\n });\n qs = qs.filter(Q.or(...searchFilters));\n }\n\n const ordering = params.getOrdering();\n if (ordering.length > 0) {\n const orderTokens = ordering.filter((field) => {\n const cleanField = field.startsWith('-') ? field.slice(1) : field;\n return this.orderingFields.includes(cleanField as keyof TModel);\n });\n if (orderTokens.length > 0) {\n qs = qs.orderBy(...orderTokens.map((token) => token as keyof TModel | `-${string & keyof TModel}`));\n }\n }\n\n qs = paginator.apply(qs);\n const resultPromise = qs.fetch();\n const totalCountPromise = paginator.needsTotalCount()\n ? qs.count()\n : Promise.resolve<number | undefined>(undefined);\n const [result, totalCount] = await Promise.all([resultPromise, totalCountPromise]);\n const serializer = this.getSerializer();\n const response = paginator.toResponse(\n result.results.map((row) => serializer.toRepresentation(row)) as SerializerOutput<TSerializer>[],\n { totalCount }\n );\n\n return TangoResponse.json(response as unknown as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n /**\n * Retrieve endpoint for a single resource by id.\n */\n async retrieve(_ctx: RequestContext, id: string): Promise<TangoResponse> {\n try {\n const manager = this.getManager();\n const pk = manager.meta.pk;\n const filterById = { [pk]: id } as FilterInput<TModel>;\n const result = await manager.query().filter(filterById).fetchOne();\n\n if (!result) {\n throw new NotFoundError(`No ${manager.meta.table} record found for ${String(pk)}=${id}.`);\n }\n\n return TangoResponse.json(this.getSerializer().toRepresentation(result) as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n /**\n * Create endpoint: validate input, persist, and return serialized output.\n */\n async create(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const body = await ctx.request.json();\n const result = await this.getSerializer().create(body);\n\n return TangoResponse.created(undefined, result as JsonValue);\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n /**\n * Update endpoint: validate partial payload and persist by id.\n */\n async update(ctx: RequestContext, id: string): Promise<TangoResponse> {\n try {\n const body = await ctx.request.json();\n const pkValue = id as TModel[keyof TModel];\n const result = await this.getSerializer().update(pkValue, body);\n\n return TangoResponse.json(result as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n /**\n * Destroy endpoint: delete a resource by id.\n */\n async destroy(_ctx: RequestContext, id: string): Promise<TangoResponse> {\n try {\n const pkValue = id as TModel[keyof TModel];\n\n await this.getManager().delete(pkValue);\n\n return TangoResponse.noContent();\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected getPaginator(queryset: QuerySet<TModel>): Paginator<TModel, SerializerOutput<TSerializer>> {\n if (this.paginatorFactory) {\n return this.paginatorFactory(queryset);\n }\n return new OffsetPaginator<TModel>(queryset) as Paginator<TModel, SerializerOutput<TSerializer>>;\n }\n\n protected getManager(): ManagerLike<TModel> {\n return this.getSerializer().getManager();\n }\n\n /**\n * Convert thrown errors into normalized HTTP responses.\n */\n protected handleError(error: unknown): TangoResponse {\n const httpError = HttpErrorFactory.toHttpError(error);\n return TangoResponse.json(httpError.body as JsonValue, { status: httpError.status });\n }\n\n /**\n * Resolve route path segment(s) for a custom action.\n * Override this in subclasses to customize path derivation globally.\n */\n protected resolveActionPath(action: ViewSetActionDescriptor): string {\n return ModelViewSet.resolvePathFromDescriptor(action.name, action.path);\n }\n\n private requireModelMetadata(): ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n } {\n const model = this.getSerializer().getModel();\n\n if (!model.metadata) {\n throw new Error('OpenAPI generation requires Tango model metadata on ModelViewSet models.');\n }\n\n return model as ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n };\n }\n\n private getLookupFieldFromMetadata(\n model: ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n }\n ): keyof TModel {\n const primaryKeyField = model.metadata.fields.find((field) => field.primaryKey);\n\n if (!primaryKeyField) {\n throw new Error('OpenAPI generation requires a primary key field in Tango model metadata.');\n }\n\n return primaryKeyField.name as keyof TModel;\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport {\n ModelViewSet,\n type ModelViewSetConfig,\n type ViewSetActionDescriptor,\n type ViewSetActionMethod,\n type ViewSetActionScope,\n type ResolvedViewSetActionDescriptor,\n} from './ModelViewSet';\nexport type { ModelViewSetOpenAPIDescription } from '../resource/index';\n"],"mappings":";;;;;;IAea,iBAAN,MAAM,eAAiC;CAC1C,OAAgB,QAAQ;CACxB,eAAqD,eAAe;CACpE,QAA+C,IAAI;CAEnD,YACoBA,SACTC,OAAqB,MACrBC,SAAiC,CAAE,GAC5C;AAAA,OAHkB,UAAA;AAAA,OACT,OAAA;AAAA,OACA,SAAA;CACP;;;;CAKJ,OAAO,iBAAmCC,OAAgD;AACtF,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,eAAe;CAE7E;;;;CAKD,OAAO,OAAyBC,SAAkBC,MAA4C;AAC1F,SAAO,IAAI,eACP,aAAa,eAAe,QAAQ,GAAG,UAAU,IAAI,aAAa,UAClE,QAAQ;CAEf;;;;CAKD,SAAYC,KAAsBC,OAAgB;AAC9C,OAAK,MAAM,IAAI,KAAK,MAAM;CAC7B;;;;CAKD,SAAYD,KAAqC;AAC7C,SAAO,KAAK,MAAM,IAAI,IAAI;CAC7B;;;;CAKD,SAASA,KAA+B;AACpC,SAAO,KAAK,MAAM,IAAI,IAAI;CAC7B;;;;CAKD,QAA+B;EAC3B,MAAM,SAAS,IAAI,eAAsB,KAAK,SAAS,KAAK,MAAM,EAAE,GAAG,KAAK,OAAQ;AACpF,SAAO,QAAQ,IAAI,IAAI,KAAK;AAC5B,SAAO;CACV;AACJ;;;;;;;;;MC5EY,qBAAqB;CAC9B,QAAQ;CACR,OAAO;CACP,OAAO;CACP,IAAI;CACJ,QAAQ;AACX;;;;ICsDY,YAAN,MAAM,UAA6C;CACtD,OAAgB,QAAQ;CACxB,eAAgD,UAAU;;;;CAK1D,YACqBE,MACAC,iBAA0B,OAC7C;AAAA,OAFmB,OAAA;AAAA,OACA,iBAAA;CACjB;;;;CAKJ,OAAO,OAA0CC,QAAgD;EAC7F,MAAM,OAAO,UAAU,sBAAsB,OAAO;AACpD,SAAO,IAAI,UAAU,MAAM,OAAO,QAAQ;CAC7C;;;;CAKD,OAAO,YAA+CC,OAAuC;AACzF,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,UAAU;CAExE;CAED,OAAe,sBACXD,QACiC;EACjC,MAAMF,OAA0C,CAAE;EAClD,MAAMI,oBAAsE,OAAO,UAAU,CAAE;EAC/F,MAAMC,eAA4D,OAAO,WAAW,CAAE;AAEtF,OAAK,MAAM,YAAY,OAAO,KAAK,kBAAkB,EAAoB;GACrE,MAAM,cAAc,kBAAkB;AACtC,OAAI,gBAAgB,UAAW;GAC/B,MAAM,SAAS,aAAa;AAC5B,aAAU,oBAAoB,MAAM,UAAU,aAAa,OAAO;EACrE;EAED,MAAM,UAAU,OAAO,WAAW,CAAE;AACpC,OAAK,MAAM,CAAC,OAAO,YAAY,IAAI,OAAO,QAAQ,QAAQ,CACtD,MAAK,SAAS,UAAU,0BAA0B,YAAY;AAGlE,SAAO;CACV;CAED,OAAe,oBACXL,MACAM,OACAC,aACAC,QACI;AACJ,MAAI,gBAAgB,MAAM;AACtB,QAAK,OAAO,MAAM,IAAI,UAAU,qBAAqB,OAAO,SAAS,OAAO;AAC5E;EACH;AAED,MAAI,UAAU,cAAc,YAAY,EAAE;AACtC,QAAK,MAAM,UAAU,aAAa;IAC9B,MAAM,QAAQ,UAAU,mBAAmB,OAAO,MAAM,EAAE,OAAO;AACjE,SAAK,SAAS,UAAU,qBAAqB,OAAO,QAAQ,OAAO;GACtE;AACD;EACH;EAED,MAAM,UAAU,YAAY,WAAW,CAAC,OAAQ;EAChD,MAAM,YAAY,YAAY,SAAS,OAAO,MAAM;EACpD,MAAM,kBAAkB,YAAY,SAAS;AAE7C,OAAK,MAAM,UAAU,SAAS;GAC1B,MAAM,QAAQ,UAAU,mBAAmB,WAAW,OAAO;AAC7D,QAAK,SAAS,UAAU,qBAAqB,OAAO,QAAQ,gBAAgB;EAC/E;CACJ;CAED,OAAe,cAAcC,OAAiE;AAC1F,SAAO,MAAM,QAAQ,MAAM;CAC9B;CAED,OAAe,0BACXC,aACiB;AACjB,MAAI,UAAU,4BAA4B,YAAY,CAClD,QAAO;AAGX,MAAI,YAAY,aAAa;GACzB,MAAM,SAAS,YAAY,UAAU;AACrC,UAAO,UAAU,yBAAyB,YAAY,QAAQ,QAAQ,YAAY,MAAM;EAC3F;AAED,SAAO,UAAU,qBAAqB,YAAY,OAAO,YAAY,UAAU,SAAS,YAAY,MAAM;CAC7G;CAED,OAAe,4BACXC,OAC0B;AAC1B,aAAW,UAAU,YAAY,UAAU,UAAU,UAAU,OAC3D,QAAO;AAGX,SAAO;GACH,mBAAmB;GACnB,mBAAmB;GACnB,mBAAmB;GACnB,mBAAmB;GACnB,mBAAmB;EACtB,EAAC,SAAS,MAAM,KAAK;CACzB;CAED,OAAe,yBACXC,QACAC,QACAC,QACiB;AACjB,MAAI,WAAW,eAAe,WAAW,UACrC,QAAO;GAAE,MAAM,mBAAmB;GAAO,SAAS,CAAC,GAAG,MAAO;EAAE;AAGnE,SAAO;GACH,MAAM,mBAAmB;GACzB,OAAO,CAAC,QAAQ;IACZ,MAAM,SAAS,UAAU,mBAAmB,KAAK,OAAO;AACxD,QAAI,WAAW,UAAW,QAAO;IAEjC,MAAMC,WAAuD,CAAE;AAC/D,SAAK,MAAM,SAAS,QAAQ;KACxB,MAAM,UAAU,UAAU,oBAAoB,OAAO,QAAQ,OAAO;AACpE,UAAK,QAAS;AACd,YAAO,OAAO,UAAU,QAAQ;IACnC;AAED,WAAO,OAAO,KAAK,SAAS,CAAC,SAAS,IAAK,WAA8B;GAC5E;EACJ;CACJ;CAED,OAAe,qBACXT,OACAO,QACAC,QACiB;AACjB,MAAI,WAAW,UACX,QAAO;GACH,MAAM,mBAAmB;GACzB,OAAO,CAAC,QAAQ;IACZ,MAAM,SAAS,UAAU,mBAAmB,KAAK,OAAO;AACxD,QAAI,WAAW,UAAW,QAAO;AACjC,WAAO,UAAU,oBAAoB,OAAO,QAAQ,OAAO;GAC9D;EACJ;AAGL,UAAQ,QAAR;AACI,QAAK,QACD,QAAO;IAAE,MAAM,mBAAmB;IAAQ,QAAQ;GAAO;AAC7D,QAAK,KACD,QAAO;IAAE,MAAM,mBAAmB;IAAI,QAAQ;GAAO;AACzD,QAAK;AACL,QAAK;AACL,QAAK;AACL,QAAK,MACD,QAAO;IAAE,MAAM,mBAAmB;IAAO,QAAQ;IAAO,IAAI;GAAQ;AACxE,QAAK,YACD,QAAO;IAAE,MAAM,mBAAmB;IAAO,SAAS,CAAC,KAAM;GAAE;AAC/D,WACI,QAAO;IACH,MAAM,mBAAmB;IACzB,OAAO,CAAC,QAAQ,UAAU,oBAAoB,OAAO,QAAQ,IAAI;GACpE;EACR;CACJ;CAED,OAAe,oBACXR,OACAO,QACAG,OAC0B;AAC1B,MAAI,UAAU,UAAW,QAAO;AAEhC,MAAI,WAAW,QACX,QAAO,GAAG,QAAQ,MAAO;AAG7B,MAAI,WAAW,MAAM;GACjB,MAAM,MAAM,MAAM,QAAQ,MAAM,GAAG,QAAQ,OAAO,MAAM,CAAC,MAAM,IAAI;GACnE,MAAM,eAAa,EAAE,OAAO,MAAM,CAAC;AACnC,UAAO,GAAG,cAAY,IAAK;EAC9B;AAED,MAAI,WAAW,aAAa;GACxB,MAAM,eAAa,EAAE,OAAO,MAAM,CAAC;AACnC,UAAO,GAAG,eAAa,GAAG,UAAU,eAAe,MAAM,CAAC,GAAI;EACjE;EAED,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC,IAAI,OAAO;AAC9C,SAAO,GAAG,YAAY,MAAsB;CAC/C;CAED,OAAe,mBAAmBC,WAAmBJ,QAA8B;AAC/E,MAAI,WAAW,QACX,QAAO;AAEX,UAAQ,EAAE,UAAU,IAAI,OAAO;CAClC;CAED,OAAe,mBACXK,OACAJ,QAC+B;AAC/B,MAAI,UAAU,UACV,QAAO;AAGX,MAAI,WAAW,UACX,QAAO;AAGX,SAAO,OAAO,MAAM;CACvB;CAED,OAAe,eAAeK,OAAoC;AAC9D,MAAI,MAAM,QAAQ,MAAM,CACpB,QAAO,MAAM,KAAK,IAAI;AAE1B,SAAO,OAAO,MAAM;CACvB;;;;CAKD,iBAAiBC,SAAoE;AACjF,MAAI,OAAO,KAAK,QAAQ,CAAC,WAAW,EAChC,QAAO;EAGX,IAAI,UAAU;EACd,MAAM,WAAW,OAAO,YACpB,OAAO,QAAQ,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,SAAS,KAAK;GACjD,MAAM,eAAe,KAAK,yBAAyB,UAAU,QAAQ;AACrE,OAAI,iBAAiB,SACjB,WAAU;AAEd,UAAO,CAAC,OAAO,YAAa;EAC/B,EAAC,CACL;AAED,SAAO,UAAU,IAAI,UAAU,UAAU,KAAK,kBAAkB;CACnE;;;;CAKD,MAAMC,QAA4C;EAC9C,MAAMC,UAA4B,CAAE;EACpC,MAAM,OAAO,IAAI;AAEjB,OAAK,MAAM,CAAC,IAAI,IAAI,OAAO,SAAS,CAChC,MAAK,IAAI,IAAI;AAGjB,OAAK,MAAM,OAAO,MAAM;GACpB,MAAM,WAAW,KAAK,KAAK,SAAS,KAAK,iBAAiB,KAAK,iBAAiB,IAAI,GAAG;AACvF,QAAK,SAAU;GAEf,MAAM,WAAW,OAAO,OAAO,IAAI;GACnC,MAAM,QAAQ,SAAS,SAAS,IAAI,WAAW,SAAS;AAExD,OAAI,UAAU,UAAW;GAEzB,MAAM,SAAS,KAAK,cAAc,UAAU,MAAM;AAClD,OAAI,OACA,SAAQ,KAAK,OAAO;EAE3B;AAED,SAAO;CACV;CAED,iBAAyBC,OAA8C;EACnE,MAAM,CAAC,UAAU,GAAG,eAAe,GAAG,MAAM,MAAM,KAAK;AACvD,OAAK,SACD,QAAO;EAGX,MAAM,QAAQ;AACd,MAAI,eAAe,WAAW,EAC1B,QAAO;GAAE,MAAM,mBAAmB;GAAQ,QAAQ;EAAO;EAG7D,MAAM,SAAS,eAAe,KAAK,KAAK;AACxC,SAAO,UAAU,qBAAqB,OAAO,OAAO;CACvD;CAED,cACIC,UACAN,OAC0B;AAC1B,MAAI,UAAU,UAAW,QAAO;AAEhC,UAAQ,SAAS,MAAjB;AACI,QAAK,mBAAmB,OACpB,QAAO,GAAG,SAAS,SAAS,MAAO;AAEvC,QAAK,mBAAmB,OAAO;IAC3B,MAAM,WAAW,GAAG,UAAU,eAAe,MAAM,CAAC;IACpD,MAAMO,SAAqD,CAAE;AAC7D,aAAS,QAAQ,QAAQ,CAAC,QAAQ;AAC9B,aAAQ,EAAE,OAAO,IAAI,CAAC,gBAAgC;IACzD,EAAC;AACF,WAAO;GACV;AAED,QAAK,mBAAmB,OAAO;IAC3B,MAAM,aAAa,EAAE,OAAO,SAAS,OAAO,CAAC,IAAI,SAAS,GAAG;AAC7D,WAAO,GAAG,YAAY,MAAO;GAChC;AAED,QAAK,mBAAmB,IAAI;IACxB,MAAM,MAAM,MAAM,QAAQ,MAAM,GAAG,QAAQ,OAAO,MAAM,CAAC,MAAM,IAAI;IACnE,MAAM,aAAa,EAAE,OAAO,SAAS,OAAO,CAAC;AAC7C,WAAO,GAAG,YAAY,IAAK;GAC9B;AAED,QAAK,mBAAmB,OACpB,QAAO,SAAS,MAAM,MAAM;AAEhC,WACI,QAAO;EACd;CACJ;CAED,yBACID,UACAJ,SACiB;AACjB,UAAQ,SAAS,MAAjB;AACI,QAAK,mBAAmB,QAAQ;IAC5B,MAAM,SAAS,QAAQ,SAAS;AAChC,WAAO,SAAS,UAAU,qBAAqB,SAAS,QAAQ,SAAS,OAAO,GAAG;GACtF;AAED,QAAK,mBAAmB,OAAO;IAC3B,MAAM,SAAS,QAAQ,SAAS;AAChC,WAAO,SAAS,UAAU,qBAAqB,SAAS,QAAQ,SAAS,IAAI,OAAO,GAAG;GAC1F;AAED,QAAK,mBAAmB,IAAI;IACxB,MAAM,SAAS,QAAQ,SAAS;AAChC,WAAO,SAAS,UAAU,qBAAqB,SAAS,QAAQ,MAAM,OAAO,GAAG;GACnF;AAED,WACI,QAAO;EACd;CACJ;AACJ;;;;;;;;;MC/ZYM,wBAA+D,IAAE,OAAO;CACjF,OAAO,IAAE,WACL,CAAC,UAAU;AACP,MAAI,UAAU,aAAa,UAAU,QAAQ,UAAU,GACnD,QAAO;EAEX,MAAM,SAAS,OAAO,SAAS,OAAO,MAAM,EAAE,GAAG;AACjD,OAAK,OAAO,SAAS,OAAO,IAAI,UAAU,EACtC,QAAO;AAEX,SAAO;CACV,GACD,IACK,QAAQ,CACR,KAAK,CACL,IAAI,EAAE,CACN,UAAU,CAAC,UAAU,KAAK,IAAI,OAAO,IAAI,CAAC,CAC1C,UAAU,CAClB;CACD,QAAQ,IAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,KAAK;CAC3C,UAAU,IAAE,QAAQ,CAAC,UAAU;AAClC,EAAC;;;;ICVI,aAAN,MAAM,WAAiC;CACnC,OAAgB,QAAQ;CACxB,eAAiD,WAAW;CAE5D,YACoBC,SACAC,YACAC,gBAClB;AAAA,OAHkB,UAAA;AAAA,OACA,aAAA;AAAA,OACA,iBAAA;CAChB;CAEJ,OAAO,aAAgBC,OAAwC;AAC3D,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,WAAW;CAEzE;;CAGD,UAAmB;AACf,SAAO,KAAK,eAAe;CAC9B;;CAGD,cAAuB;AACnB,SAAO,KAAK,mBAAmB;CAClC;CAED,iBAAgC;AAC5B,SAAO;CACV;CAED,qBAAoC;AAChC,SAAO;CACV;CAED,aAAqB;AACjB,SAAO;CACV;CAED,WAAmB;AACf,SAAO,KAAK,QAAQ;CACvB;AACJ;IAOY,kBAAN,MAAM,gBAA0G;CACnH,OAAgB,QAAQ;CACxB,eAAsD,gBAAgB;CACtE;CACA,SAAgC;CAChC,YAAqC;CACrC,aAAoC;CACpC,iBAAwC;CAExC,YACYC,UACAC,UAAkB,IAClBC,cAAuB,MACjC;AAAA,OAHU,WAAA;AAAA,OACA,UAAA;AAAA,OACA,cAAA;AAER,OAAK,QAAQ;CAChB;;;;CAKD,OAAO,kBAAqDH,OAA6C;AACrG,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,gBAAgB;CAE9E;;;;CAKD,MAAMI,QAAgC;EAClC,MAAM,SAAS,sBAAsB,MAAM;GACvC,OAAO,OAAO,IAAI,QAAQ,IAAI;GAC9B,QAAQ,OAAO,IAAI,SAAS;GAC5B,UAAU,OAAO,IAAI,WAAW,IAAI;EACvC,EAAC;AAEF,OAAK,QAAQ,OAAO,SAAS,KAAK;AAClC,OAAK,SAAS,OAAO;EAErB,MAAM,WAAW,OAAO;AACxB,MAAI,UAAU;GACV,MAAMC,kBAAmC,SAAS,WAAW,IAAI,GAAG,SAAS;GAC7E,MAAM,cAAc,SAAS,WAAW,IAAI,GAAG,SAAS,MAAM,EAAE,GAAG;AACnE,QAAK,YAAY,gBAAgB,OAAO,KAAK,YAAY,GAAG,kBAAkB;EACjF,MACG,MAAK,YAAY;CAExB;;;;CAKD,YAAYD,QAA6D;AACrE,OAAK,MAAM,OAAO;AAClB,SAAO;GAAE,OAAO,KAAK;GAAO,QAAQ;EAAG;CAC1C;;;;CAKD,kBAA2B;AACvB,SAAO;CACV;CAED,WACIE,SACAC,UACgC;EAChC,MAAMC,WAA6C,EAAE,QAAS;AAC9D,MAAI,KAAK,WACL,UAAS,OAAO,KAAK,cAAc,KAAK,WAAW;AAEvD,MAAI,KAAK,eACL,UAAS,WAAW,KAAK,cAAc,KAAK,eAAe;AAE/D,SAAO;CACV;;;;CAKD,qBAA8BF,SAAoBG,aAAwD;AACtG,SAAO,KAAK,WAAW,QAAQ;CAClC;;;;CAKD,MACIC,UACiD;EACjD,IAAI,KAAK,SAAS,MAAM,KAAK,QAAQ,EAAE;AACvC,MAAI,KAAK,QAAQ;GACb,MAAM,UAAU,KAAK,aAAa,KAAK,OAAO;AAC9C,OAAI,QAAQ,UAAU,OAAO,KAAK,YAAY,CAC1C,OAAM,IAAI,MAAM;GAEpB,MAAM,SAAS,KAAK,cAAc,QAAQ,SAAS;GACnD,MAAM,eAAe,EAAE,OAAO,KAAK,YAAY,CAAC,EAAE,OAAO;GACzD,MAAM,cAAc,GAAG,cAAc,QAAQ,MAAO;AACpD,QAAK,GAAG,OAAO,YAAY;EAC9B;EACD,MAAM,aAAa,KAAK,cAAc,QAAQ,OAAO,KAAK,YAAY,IAAI,GAAG,OAAO,KAAK,YAAY,CAAC;AACtG,SAAO,GAAG,QAAQ,WAAqC;CAC1D;;;;CAKD,MAAM,SAASC,QAAmC;EAC9C,MAAM,gBAAgB,UAAU,KAAK;AACrC,OAAK,SAAS;EACd,MAAM,UAAU,MAAM,KAAK,MAAM,KAAK,SAAS,CAAC,OAAO;EACvD,MAAM,UAAU,CAAC,GAAG,QAAQ,OAAQ;EACpC,MAAM,UAAU,QAAQ,SAAS,KAAK;AAEtC,MAAI,QACA,SAAQ,KAAK;AAGjB,OAAK,iBAAiB,iBAAiB;EACvC,MAAM,OAAO,QAAQ,GAAA,GAAM;AAC3B,OAAK,aAAa,WAAW,OAAO,KAAK,aAAa,KAAK,GAAG;AAE9D,SAAO,IAAI,WAAW,SAAS,KAAK,YAAY,KAAK;CACxD;;;;CAKD,MAAM,QAAQC,MAAgC;AAC1C,MAAI,SAAS,EACT,OAAM,IAAI,MAAM;AAEpB,SAAO,KAAK,UAAU;CACzB;CAED,cAAsBC,QAAwB;EAC1C,MAAM,gBAAgB,KAAK,cAAc,QAAQ,OAAO,KAAK,YAAY,IAAI,GAAG,OAAO,KAAK,YAAY,CAAC;AACzG,UAAQ,SAAS,KAAK,MAAM,UAAU,mBAAmB,OAAO,CAAC,YAAY,mBAAmB,cAAc,CAAC;CAClH;CAED,aAAqBC,MAAiB;EAClC,MAAMC,UAAyB;GAC3B,GAAG;GACH,OAAO,OAAO,KAAK,YAAY;GAC/B,KAAK,KAAK;GACV,OAAO,KAAK,KAAK;EACpB;AACD,SAAO,OAAO,KAAK,KAAK,UAAU,QAAQ,EAAE,QAAQ,CAAC,SAAS,SAAS;CAC1E;CAED,aAAqBF,QAA+B;EAChD,IAAIG;AACJ,MAAI;AACA,YAAS,KAAK,MAAM,OAAO,KAAK,QAAQ,SAAS,CAAC,SAAS,QAAQ,CAAC;EACvE,QAAO;AACJ,SAAM,IAAI,MAAM;EACnB;AAED,OACK,iBACM,WAAW,YACjB,OAA2B,MAAM,YAC1B,OAA+B,UAAU,YAC/C,OAA6B,QAAQ,SAAU,OAA6B,QAAQ,YACpF,WAAW,QAEb,OAAM,IAAI,MAAM;AAGpB,SAAO;CACV;AACJ;;;;;;;;;;;;;;;;;;;;;;;;;;ICxNqB,aAAf,MAIL;CACE,OAAgB,eAAiC,EAAE,SAAS;CAC5D,OAAgB,eAAiC,EAAE,SAAS;CAC5D,OAAgB,eAAiC,EAAE,SAAS;;;;CAK5D,qBAAmF;AAC/E,SAAO,KAAK;CACf;;;;CAKD,kBAAiC;AAC7B,SAAO,KAAK,oBAAoB,CAAC;CACpC;;;;CAKD,kBAAiC;AAC7B,SAAO,KAAK,oBAAoB,CAAC;CACpC;;;;CAKD,kBAAiC;AAC7B,SAAO,KAAK,oBAAoB,CAAC;CACpC;;;;CAKD,kBAAkBC,OAAyC;AACvD,SAAO,KAAK,iBAAiB,CAAC,MAAM,MAAM;CAC7C;;;;CAKD,kBAAkBA,OAAyC;AACvD,SAAO,KAAK,iBAAiB,CAAC,MAAM,MAAM;CAC7C;;;;CAKD,iBAAiBC,QAA0C;AACvD,SAAO,KAAK,iBAAiB,CAAC,MAAM,OAAO;CAC9C;AACJ;;;;ICrDqB,kBAAf,cAKG,WAAwD;CAC9D,OAAgB;;;;CAKhB,WAAsC;EAClC,MAAM,QACF,KAAK,YACP;AAEF,OAAK,MACD,OAAM,IAAI,OAAO,EAAE,KAAK,YAAY,KAAK;AAG7C,SAAO;CACV;;;;CAKD,aAAkC;AAC9B,SAAO,KAAK,UAAU,CAAC;CAC1B;;;;CAKD,MAAM,OACFC,OACoG;EACpG,MAAM,YAAY,KAAK,kBAAkB,MAAM;EAC/C,MAAM,WAAW,MAAM,KAAK,aAAa,UAAU;EACnD,MAAM,UAAU,MAAM,KAAK,YAAY,CAAC,OAAO,SAAS;AACxD,SAAO,KAAK,iBAAiB,QAAQ;CACxC;;;;CAKD,MAAM,OACFC,IACAD,OACoG;EACpG,MAAM,YAAY,KAAK,kBAAkB,MAAM;EAC/C,MAAM,WAAW,MAAM,KAAK,aAAa,IAAI,UAAU;EACvD,MAAM,UAAU,MAAM,KAAK,YAAY,CAAC,OAAO,IAAI,SAAS;AAC5D,SAAO,KAAK,iBAAiB,QAAQ;CACxC;;;;;;;;CASD,MAAgB,aACZE,MACwB;AACxB,SAAO;CACV;;;;;;;;CASD,MAAgB,aACZC,KACAC,MACwB;AACxB,SAAO;CACV;AACJ;;;;;;;;;;;;ICpDqB,eAAf,MAAe,aAGpB;CACE,OAAgB,QAAQ;CACxB,OAAgB,UAA8C,CAAE;CAChE,eAAmD,aAAa;CAChE;CACA;CACA;CACA;CACA;CAGA;CAEA,YAAYC,QAAiD;AACzD,OAAK,kBAAkB,OAAO;AAC9B,OAAK,UAAU,OAAO;AACtB,OAAK,iBAAiB,OAAO,kBAAkB,CAAE;AACjD,OAAK,eAAe,OAAO,gBAAgB,CAAE;AAC7C,OAAK,mBAAmB,OAAO;CAClC;;;;CAKD,OAAO,WACHC,sBAC0C;EAC1C,MAAM,UAAU,aAAa,eAAe,qBAAqB,GAAG,uBAAuB;EAE3F,MAAM,mBAAmB,UAClB,QAAQ,cACR;EACP,MAAM,UAAU,MAAM,QAAQ,iBAAiB,QAAQ,GAAG,iBAAiB,UAAU,CAAE;AAEvF,SAAO,QAAQ,IAAI,CAAC,YAAY;GAC5B,GAAG;GACH,MAAM,UACA,QAAQ,kBAAkB,OAAO,GACjC,aAAa,0BAA0B,OAAO,MAAM,OAAO,KAAK;EACzE,GAAE;CACN;;;;CAKD,OAAO,eAAeC,OAAyF;AAC3G,gBACW,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB,aAAa;CAE3E;;;;CAKD,OAAO,qBAAyEC,SAAe;AAC3F,SAAO;CACV;CAED,OAAe,0BAA0BC,MAAcC,cAA+B;EAClF,MAAM,aAAa,CAAC,cAAc,MAAM,IAAI,aAAa,YAAY,KAAK,EAAE,QAAQ,cAAc,GAAG;AACrG,OAAK,WACD,OAAM,IAAI,OAAO,kCAAkC,KAAK;AAE5D,SAAO;CACV;CAED,OAAe,YAAYC,OAAuB;AAC9C,SAAO,MACF,QAAQ,sBAAsB,QAAQ,CACtC,QAAQ,WAAW,IAAI,CACvB,aAAa;CACrB;;;;CAKD,qBAAkC;AAC9B,SAAO,KAAK;CACf;;;;CAKD,gBAA2C;AACvC,OAAK,KAAK,WACN,MAAK,aAAa,IAAI,KAAK;AAG/B,SAAO,KAAK;CACf;;;;CAKD,kBAAuE;EACnE,MAAM,QAAQ,KAAK,sBAAsB;AACzC,SAAO;GACH;GACA,cAAc,KAAK,eAAe,CAAC,iBAAiB;GACpD,cAAc,KAAK,eAAe,CAAC,iBAAiB;GACpD,cAAc,KAAK,eAAe,CAAC,iBAAiB;GACpD,cAAc,KAAK;GACnB,gBAAgB,KAAK;GACrB,aAAa,KAAK,2BAA2B,MAAM;GACnD,aAAa;GACb,gBAAgB;IAAC;IAAO;IAAQ;IAAO;IAAS;GAAS;GACzD,8BAA8B,KAAK;GACnC,SAAS,aAAa,WAAW,KAAmC;EACvE;CACJ;;;;CAKD,MAAM,KAAKC,KAA6C;AACpD,MAAI;GACA,MAAM,SAAS,IAAI,QAAQ;GAC3B,MAAM,eAAe,KAAK,YAAY,CAAC,OAAO;GAC9C,MAAM,YAAY,KAAK,aAAa,aAAa;AACjD,aAAU,MAAM,OAAO;GAEvB,IAAI,KAAK;AAET,OAAI,KAAK,SAAS;IACd,MAAM,eAAe,KAAK,QACrB,iBAAiB,uBAAuB,KAAK,eAAe,CAAC,UAAU,CAAC,CAAC,CACzE,MAAM,OAAO;AAClB,QAAI,aAAa,SAAS,EACtB,MAAK,GAAG,OAAO,EAAE,IAAI,GAAG,aAAa,CAAC;GAE7C;GAED,MAAM,SAAS,OAAO,WAAW;AACjC,OAAI,UAAU,KAAK,aAAa,SAAS,GAAG;IACxC,MAAMC,gBAAuC,KAAK,aAAa,IAAI,CAAC,UAAU;KAC1E,MAAM,UAAU,EAAE,OAAO,MAAM,CAAC;AAChC,YAAO,GAAG,SAAS,OAAQ;IAC9B,EAAC;AACF,SAAK,GAAG,OAAO,EAAE,GAAG,GAAG,cAAc,CAAC;GACzC;GAED,MAAM,WAAW,OAAO,aAAa;AACrC,OAAI,SAAS,SAAS,GAAG;IACrB,MAAM,cAAc,SAAS,OAAO,CAAC,UAAU;KAC3C,MAAM,aAAa,MAAM,WAAW,IAAI,GAAG,MAAM,MAAM,EAAE,GAAG;AAC5D,YAAO,KAAK,eAAe,SAAS,WAA2B;IAClE,EAAC;AACF,QAAI,YAAY,SAAS,EACrB,MAAK,GAAG,QAAQ,GAAG,YAAY,IAAI,CAAC,UAAU,MAAoD,CAAC;GAE1G;AAED,QAAK,UAAU,MAAM,GAAG;GACxB,MAAM,gBAAgB,GAAG,OAAO;GAChC,MAAM,oBAAoB,UAAU,iBAAiB,GAC/C,GAAG,OAAO,GACV,QAAQ,QAA4B,UAAU;GACpD,MAAM,CAAC,QAAQ,WAAW,GAAG,MAAM,QAAQ,IAAI,CAAC,eAAe,iBAAkB,EAAC;GAClF,MAAM,aAAa,KAAK,eAAe;GACvC,MAAM,WAAW,UAAU,WACvB,OAAO,QAAQ,IAAI,CAAC,QAAQ,WAAW,iBAAiB,IAAI,CAAC,EAC7D,EAAE,WAAY,EACjB;AAED,UAAO,cAAc,KAAK,UAAkC,EAAE,QAAQ,IAAK,EAAC;EAC/E,SAAQ,OAAO;AACZ,UAAO,KAAK,YAAY,MAAM;EACjC;CACJ;;;;CAKD,MAAM,SAASC,MAAsBC,IAAoC;AACrE,MAAI;GACA,MAAM,UAAU,KAAK,YAAY;GACjC,MAAM,KAAK,QAAQ,KAAK;GACxB,MAAM,aAAa,GAAG,KAAK,GAAI;GAC/B,MAAM,SAAS,MAAM,QAAQ,OAAO,CAAC,OAAO,WAAW,CAAC,UAAU;AAElE,QAAK,OACD,OAAM,IAAI,eAAe,KAAK,QAAQ,KAAK,MAAM,oBAAoB,OAAO,GAAG,CAAC,GAAG,GAAG;AAG1F,UAAO,cAAc,KAAK,KAAK,eAAe,CAAC,iBAAiB,OAAO,EAAe,EAAE,QAAQ,IAAK,EAAC;EACzG,SAAQ,OAAO;AACZ,UAAO,KAAK,YAAY,MAAM;EACjC;CACJ;;;;CAKD,MAAM,OAAOH,KAA6C;AACtD,MAAI;GACA,MAAM,OAAO,MAAM,IAAI,QAAQ,MAAM;GACrC,MAAM,SAAS,MAAM,KAAK,eAAe,CAAC,OAAO,KAAK;AAEtD,UAAO,cAAc,QAAQ,WAAW,OAAoB;EAC/D,SAAQ,OAAO;AACZ,UAAO,KAAK,YAAY,MAAM;EACjC;CACJ;;;;CAKD,MAAM,OAAOA,KAAqBG,IAAoC;AAClE,MAAI;GACA,MAAM,OAAO,MAAM,IAAI,QAAQ,MAAM;GACrC,MAAM,UAAU;GAChB,MAAM,SAAS,MAAM,KAAK,eAAe,CAAC,OAAO,SAAS,KAAK;AAE/D,UAAO,cAAc,KAAK,QAAqB,EAAE,QAAQ,IAAK,EAAC;EAClE,SAAQ,OAAO;AACZ,UAAO,KAAK,YAAY,MAAM;EACjC;CACJ;;;;CAKD,MAAM,QAAQD,MAAsBC,IAAoC;AACpE,MAAI;GACA,MAAM,UAAU;AAEhB,SAAM,KAAK,YAAY,CAAC,OAAO,QAAQ;AAEvC,UAAO,cAAc,WAAW;EACnC,SAAQ,OAAO;AACZ,UAAO,KAAK,YAAY,MAAM;EACjC;CACJ;CAED,aAAuBC,UAA8E;AACjG,MAAI,KAAK,iBACL,QAAO,KAAK,iBAAiB,SAAS;AAE1C,SAAO,IAAI,gBAAwB;CACtC;CAED,aAA4C;AACxC,SAAO,KAAK,eAAe,CAAC,YAAY;CAC3C;;;;CAKD,YAAsBC,OAA+B;EACjD,MAAM,YAAY,iBAAiB,YAAY,MAAM;AACrD,SAAO,cAAc,KAAK,UAAU,MAAmB,EAAE,QAAQ,UAAU,OAAQ,EAAC;CACvF;;;;;CAMD,kBAA4BC,QAAyC;AACjE,SAAO,aAAa,0BAA0B,OAAO,MAAM,OAAO,KAAK;CAC1E;CAED,uBAEE;EACE,MAAM,QAAQ,KAAK,eAAe,CAAC,UAAU;AAE7C,OAAK,MAAM,SACP,OAAM,IAAI,MAAM;AAGpB,SAAO;CAGV;CAED,2BACIC,OAGY;EACZ,MAAM,kBAAkB,MAAM,SAAS,OAAO,KAAK,CAAC,UAAU,MAAM,WAAW;AAE/E,OAAK,gBACD,OAAM,IAAI,MAAM;AAGpB,SAAO,gBAAgB;CAC1B;AACJ"}
@@ -12,9 +12,10 @@ export interface Page<T> {
12
12
  }
13
13
  export interface Paginator<TModel extends Record<string, unknown>, TResult = TModel, TResponse extends PaginatedResponse<TResult> = PaginatedResponse<TResult>> {
14
14
  parse(params: TangoQueryParams): void;
15
- apply(queryset: QuerySet<TModel>): QuerySet<TModel>;
15
+ apply<TBaseResult extends Record<string, unknown>, TSourceModel, THydrated extends Record<string, unknown>>(queryset: QuerySet<TModel, TBaseResult, TSourceModel, THydrated>): QuerySet<TModel, TBaseResult, TSourceModel, THydrated>;
16
16
  needsTotalCount(): boolean;
17
17
  toResponse(results: TResult[], context?: {
18
18
  totalCount?: number;
19
+ params?: TangoQueryParams;
19
20
  }): TResponse;
20
21
  }
@@ -38,7 +38,10 @@ export declare class CursorPaginator<T extends Record<string, unknown>> implemen
38
38
  * Build a paginated response payload with cursor links.
39
39
  */
40
40
  needsTotalCount(): boolean;
41
- toResponse<TResult>(results: TResult[]): CursorPaginatedResponse<TResult>;
41
+ toResponse<TResult>(results: TResult[], _context?: {
42
+ totalCount?: number;
43
+ params?: TangoQueryParams;
44
+ }): CursorPaginatedResponse<TResult>;
42
45
  /**
43
46
  * Backward-compatible alias for `toResponse`.
44
47
  */
@@ -46,7 +49,7 @@ export declare class CursorPaginator<T extends Record<string, unknown>> implemen
46
49
  /**
47
50
  * Apply cursor constraints and ordering to a queryset.
48
51
  */
49
- apply(queryset: QuerySet<T>): QuerySet<T>;
52
+ apply<TBaseResult extends Record<string, unknown>, TSourceModel, THydrated extends Record<string, unknown>>(queryset: QuerySet<T, TBaseResult, TSourceModel, THydrated>): QuerySet<T, TBaseResult, TSourceModel, THydrated>;
50
53
  /**
51
54
  * Fetch the next cursor page.
52
55
  */
@@ -47,15 +47,16 @@ export declare class OffsetPaginator<T extends Record<string, unknown>> implemen
47
47
  needsTotalCount(): boolean;
48
48
  toResponse<TResult>(results: TResult[], context?: {
49
49
  totalCount?: number;
50
+ params?: TangoQueryParams;
50
51
  }): OffsetPaginatedResponse<TResult>;
51
52
  /**
52
53
  * Backward-compatible alias for `toResponse`.
53
54
  */
54
- getPaginatedResponse<TResult>(results: TResult[], totalCount?: number): OffsetPaginatedResponse<TResult>;
55
+ getPaginatedResponse<TResult>(results: TResult[], totalCount?: number, params?: TangoQueryParams): OffsetPaginatedResponse<TResult>;
55
56
  /**
56
57
  * Apply current limit/offset to a queryset.
57
58
  */
58
- apply(queryset: QuerySet<T>): QuerySet<T>;
59
+ apply<TBaseResult extends Record<string, unknown>, TSourceModel, THydrated extends Record<string, unknown>>(queryset: QuerySet<T, TBaseResult, TSourceModel, THydrated>): QuerySet<T, TBaseResult, TSourceModel, THydrated>;
59
60
  /**
60
61
  * Fetch a 1-based page number from the bound queryset.
61
62
  */
@@ -68,4 +69,5 @@ export declare class OffsetPaginator<T extends Record<string, unknown>> implemen
68
69
  * Count total rows for the current queryset state.
69
70
  */
70
71
  count(): Promise<number>;
72
+ private buildPageLink;
71
73
  }
@@ -1,3 +1,3 @@
1
- import { APIView, CreateAPIView, CreateModelMixin, DestroyModelMixin, GenericAPIView, ListAPIView, ListCreateAPIView, ListModelMixin, RetrieveAPIView, RetrieveDestroyAPIView, RetrieveModelMixin, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, UpdateModelMixin } from "../view-CTogofVY.js";
1
+ import { APIView, CreateAPIView, CreateModelMixin, DestroyModelMixin, GenericAPIView, ListAPIView, ListCreateAPIView, ListModelMixin, RetrieveAPIView, RetrieveDestroyAPIView, RetrieveModelMixin, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, UpdateModelMixin } from "../view-8PdNL5Ts.js";
2
2
 
3
3
  export { APIView, CreateAPIView, CreateModelMixin, DestroyModelMixin, GenericAPIView, ListAPIView, ListCreateAPIView, ListModelMixin, RetrieveAPIView, RetrieveDestroyAPIView, RetrieveModelMixin, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, UpdateModelMixin };
@@ -113,10 +113,10 @@ var OffsetPaginator = class OffsetPaginator {
113
113
  const response = { results };
114
114
  if (totalCount !== undefined) {
115
115
  response.count = totalCount;
116
- if (this.offset + this.limit < totalCount) response.next = `?limit=${this.limit}&offset=${this.offset + this.limit}`;
116
+ if (this.offset + this.limit < totalCount) response.next = this.buildPageLink(this.offset + this.limit, context?.params);
117
117
  if (this.offset > 0) {
118
118
  const prevOffset = Math.max(0, this.offset - this.limit);
119
- response.previous = `?limit=${this.limit}&offset=${prevOffset}`;
119
+ response.previous = this.buildPageLink(prevOffset, context?.params);
120
120
  }
121
121
  }
122
122
  return response;
@@ -124,8 +124,11 @@ var OffsetPaginator = class OffsetPaginator {
124
124
  /**
125
125
  * Backward-compatible alias for `toResponse`.
126
126
  */
127
- getPaginatedResponse(results, totalCount) {
128
- return this.toResponse(results, { totalCount });
127
+ getPaginatedResponse(results, totalCount, params) {
128
+ return this.toResponse(results, {
129
+ totalCount,
130
+ params
131
+ });
129
132
  }
130
133
  /**
131
134
  * Apply current limit/offset to a queryset.
@@ -154,6 +157,14 @@ var OffsetPaginator = class OffsetPaginator {
154
157
  async count() {
155
158
  return this.queryset.count();
156
159
  }
160
+ buildPageLink(offset, params) {
161
+ if (!params) return `?limit=${this.limit}&offset=${offset}`;
162
+ return params.withValues({
163
+ limit: this.limit,
164
+ offset,
165
+ page: null
166
+ }).toRelativeURL();
167
+ }
157
168
  };
158
169
 
159
170
  //#endregion
@@ -613,4 +624,4 @@ __export(view_exports, {
613
624
 
614
625
  //#endregion
615
626
  export { APIView, CreateAPIView, CreateModelMixin, DestroyModelMixin, GenericAPIView, ListAPIView, ListCreateAPIView, ListModelMixin, OffsetPaginationInput, OffsetPaginator, RetrieveAPIView, RetrieveDestroyAPIView, RetrieveModelMixin, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, UpdateModelMixin, __export, inferModelFieldParsers, view_exports };
616
- //# sourceMappingURL=view-CTogofVY.js.map
627
+ //# sourceMappingURL=view-8PdNL5Ts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-8PdNL5Ts.js","names":["OffsetPaginationInput: z.ZodType<OffsetPaginationInputValue>","results: T[]","pageNumber: number","perPage: number","totalCount?: number","value: unknown","queryset: QuerySet<T>","params: TangoQueryParams","results: TResult[]","context?: { totalCount?: number; params?: TangoQueryParams }","response: OffsetPaginatedResponse<TResult>","params?: TangoQueryParams","queryset: QuerySet<T, TBaseResult, TSourceModel, THydrated>","page: number","offset: number","raw: string | string[]","model: ResourceModelLike<T>","parsers: Partial<Record<keyof T, FilterValueParser>>","value: unknown","ctx: RequestContext","allowed: APIViewMethod[]","_ctx: RequestContext","method: APIViewMethod","method: string","config: GenericAPIViewConfig<TModel, TSerializer>","ctx: RequestContext","queryset: QuerySet<TModel>","searchFilters: FilterInput<TModel>[]","error: unknown","model: ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n }","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext"],"sources":["../src/pagination/OffsetPaginationInput.ts","../src/paginators/OffsetPaginator.ts","../src/filters/inferModelFieldParsers.ts","../src/view/APIView.ts","../src/view/GenericAPIView.ts","../src/view/mixins/ListModelMixin.ts","../src/view/mixins/CreateModelMixin.ts","../src/view/mixins/RetrieveModelMixin.ts","../src/view/mixins/UpdateModelMixin.ts","../src/view/mixins/DestroyModelMixin.ts","../src/view/generics/ListAPIView.ts","../src/view/generics/CreateAPIView.ts","../src/view/generics/RetrieveAPIView.ts","../src/view/generics/ListCreateAPIView.ts","../src/view/generics/RetrieveUpdateAPIView.ts","../src/view/generics/RetrieveDestroyAPIView.ts","../src/view/generics/RetrieveUpdateDestroyAPIView.ts","../src/view/index.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport type OffsetPaginationInputValue = {\n limit: number;\n offset: number;\n page?: number;\n};\n\nexport const OffsetPaginationInput: z.ZodType<OffsetPaginationInputValue> = z.object({\n limit: z.coerce\n .number()\n .int()\n .min(1)\n .default(25)\n .transform((value) => Math.min(value, 100)),\n offset: z.coerce.number().int().min(0).default(0),\n page: z.coerce.number().int().min(1).optional(),\n});\n","import { TangoQueryParams } from '@danceroutine/tango-core';\nimport type { QuerySet } from '@danceroutine/tango-orm';\nimport type { Paginator, Page } from '../pagination/Paginator';\nimport type { OffsetPaginatedResponse } from '../pagination/PaginatedResponse';\nimport { OffsetPaginationInput } from '../pagination/OffsetPaginationInput';\n\nclass OffsetPage<T> implements Page<T> {\n static readonly BRAND = 'tango.resources.offset_page' as const;\n readonly __tangoBrand: typeof OffsetPage.BRAND = OffsetPage.BRAND;\n\n constructor(\n public readonly results: T[],\n private readonly pageNumber: number,\n private readonly perPage: number,\n private readonly totalCount?: number\n ) {}\n\n static isOffsetPage<T>(value: unknown): value is OffsetPage<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === OffsetPage.BRAND\n );\n }\n\n /** Whether a next page exists based on known total count. */\n hasNext(): boolean {\n if (this.totalCount === undefined) {\n return false;\n }\n return this.endIndex() < this.totalCount;\n }\n\n /** Whether a previous page exists. */\n hasPrevious(): boolean {\n return this.pageNumber > 1;\n }\n\n /** The next page number, if available. */\n nextPageNumber(): number | null {\n return this.hasNext() ? this.pageNumber + 1 : null;\n }\n\n /** The previous page number, if available. */\n previousPageNumber(): number | null {\n return this.hasPrevious() ? this.pageNumber - 1 : null;\n }\n\n /** Zero-based start index of this page in the full result set. */\n startIndex(): number {\n return (this.pageNumber - 1) * this.perPage;\n }\n\n /** Exclusive end index of this page in the full result set. */\n endIndex(): number {\n return this.startIndex() + this.results.length;\n }\n}\n\n/**\n * Offset/limit paginator modelled after DRF's LimitOffsetPagination.\n * Handles parsing limit/offset/page from URL query params and building\n * the paginated response envelope with next/previous links.\n *\n * @example\n * ```typescript\n * const paginator = new OffsetPaginator(queryset);\n * const { limit, offset } = paginator.parseParams(searchParams);\n * const results = await queryset.limit(limit).offset(offset).fetchAll();\n * const response = paginator.getPaginatedResponse(results, totalCount);\n * ```\n */\nexport class OffsetPaginator<T extends Record<string, unknown>> implements Paginator<T, T, OffsetPaginatedResponse<T>> {\n static readonly BRAND = 'tango.resources.offset_paginator' as const;\n readonly __tangoBrand: typeof OffsetPaginator.BRAND = OffsetPaginator.BRAND;\n private limit = 25;\n private offset = 0;\n\n constructor(\n private queryset: QuerySet<T>,\n private perPage: number = 25\n ) {\n this.limit = perPage;\n }\n\n /**\n * Narrow an unknown value to `OffsetPaginator`.\n */\n static isOffsetPaginator<T extends Record<string, unknown>>(value: unknown): value is OffsetPaginator<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === OffsetPaginator.BRAND\n );\n }\n\n /**\n * Parse limit, offset, and page from Tango query params.\n * If `page` is provided, it's converted to an offset.\n * Stores parsed values for use by getPaginatedResponse.\n */\n parse(params: TangoQueryParams): void {\n const input = {\n limit: params.get('limit') ?? undefined,\n offset: params.get('offset') ?? undefined,\n page: params.get('page') ?? undefined,\n };\n\n const parsed = OffsetPaginationInput.parse(input);\n\n if (parsed.page) {\n parsed.offset = (parsed.page - 1) * parsed.limit;\n }\n\n this.limit = parsed.limit;\n this.offset = parsed.offset;\n }\n\n /**\n * Parse params and return `{ limit, offset }` for compatibility callers.\n */\n parseParams(params: TangoQueryParams): { limit: number; offset: number } {\n this.parse(params);\n return { limit: this.limit, offset: this.offset };\n }\n\n /**\n * Build a DRF-style paginated response with count, next, and previous links.\n * Uses the limit/offset stored from the most recent parseParams call.\n */\n needsTotalCount(): boolean {\n return true;\n }\n\n toResponse<TResult>(\n results: TResult[],\n context?: { totalCount?: number; params?: TangoQueryParams }\n ): OffsetPaginatedResponse<TResult> {\n const totalCount = context?.totalCount;\n const response: OffsetPaginatedResponse<TResult> = { results };\n\n if (totalCount !== undefined) {\n response.count = totalCount;\n\n if (this.offset + this.limit < totalCount) {\n response.next = this.buildPageLink(this.offset + this.limit, context?.params);\n }\n\n if (this.offset > 0) {\n const prevOffset = Math.max(0, this.offset - this.limit);\n response.previous = this.buildPageLink(prevOffset, context?.params);\n }\n }\n\n return response;\n }\n\n /**\n * Backward-compatible alias for `toResponse`.\n */\n getPaginatedResponse<TResult>(\n results: TResult[],\n totalCount?: number,\n params?: TangoQueryParams\n ): OffsetPaginatedResponse<TResult> {\n return this.toResponse(results, { totalCount, params });\n }\n\n /**\n * Apply current limit/offset to a queryset.\n */\n apply<TBaseResult extends Record<string, unknown>, TSourceModel, THydrated extends Record<string, unknown>>(\n queryset: QuerySet<T, TBaseResult, TSourceModel, THydrated>\n ): QuerySet<T, TBaseResult, TSourceModel, THydrated> {\n return queryset.limit(this.limit).offset(this.offset);\n }\n\n /**\n * Fetch a 1-based page number from the bound queryset.\n */\n async paginate(page: number): Promise<Page<T>> {\n return this.getPage(page);\n }\n\n /**\n * Fetch a 1-based page and return page metadata.\n */\n async getPage(page: number): Promise<Page<T>> {\n const offset = (page - 1) * this.perPage;\n const results = await this.queryset.offset(offset).limit(this.perPage).fetch();\n\n const totalCount = await this.count();\n\n return new OffsetPage(results.results, page, this.perPage, totalCount);\n }\n\n /**\n * Count total rows for the current queryset state.\n */\n async count(): Promise<number> {\n return this.queryset.count();\n }\n\n private buildPageLink(offset: number, params?: TangoQueryParams): string {\n if (!params) {\n return `?limit=${this.limit}&offset=${offset}`;\n }\n\n return params\n .withValues({\n limit: this.limit,\n offset,\n page: null,\n })\n .toRelativeURL();\n }\n}\n","import type { ResourceModelLike } from '../resource/index';\nimport type { FilterValueParser } from './FilterSet';\n\nfunction normalizeParserTokens(raw: string | string[]): string[] {\n const tokens = Array.isArray(raw) ? raw : String(raw).split(',');\n const normalized = tokens.map((value) => value.trim());\n return normalized.every((value) => value.length > 0) ? normalized : [];\n}\n\nfunction createBooleanParser(): FilterValueParser {\n return (raw) => {\n const values = normalizeParserTokens(raw);\n if (values.length === 0) {\n return undefined;\n }\n\n const parsed = values.map((value) => {\n const normalized = value.toLowerCase();\n\n if (normalized === 'true' || normalized === '1') {\n return true;\n }\n\n if (normalized === 'false' || normalized === '0') {\n return false;\n }\n\n return null;\n });\n\n if (parsed.some((value) => value === null)) {\n return undefined;\n }\n\n return parsed.length === 1 ? parsed[0]! : (parsed as boolean[]);\n };\n}\n\nfunction createIntegerParser(): FilterValueParser {\n return (raw) => {\n const values = normalizeParserTokens(raw);\n if (values.length === 0) {\n return undefined;\n }\n\n const parsed = values.map(Number);\n\n if (parsed.some((value) => !Number.isInteger(value))) {\n return undefined;\n }\n\n return parsed.length === 1 ? parsed[0] : parsed;\n };\n}\n\nfunction createTimestampParser(): FilterValueParser {\n return (raw) => {\n const values = normalizeParserTokens(raw);\n if (values.length === 0) {\n return undefined;\n }\n\n const parsed = values.map((value) => {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n });\n\n if (parsed.some((value) => value === null)) {\n return undefined;\n }\n\n return parsed.length === 1 ? parsed[0]! : (parsed as Date[]);\n };\n}\n\n/**\n * Infer resource-level query-value parsers from Tango model metadata.\n *\n * Parsers are inferred conservatively from field metadata so HTTP query filters\n * can be coerced into typed ORM inputs without framework-specific glue.\n */\nexport function inferModelFieldParsers<T extends Record<string, unknown>>(\n model: ResourceModelLike<T>\n): Partial<Record<keyof T, FilterValueParser>> {\n const metadata = model.metadata;\n if (!metadata) {\n return {};\n }\n\n const parsers: Partial<Record<keyof T, FilterValueParser>> = {};\n\n for (const field of metadata.fields) {\n switch (field.type) {\n case 'bool':\n parsers[field.name as keyof T] = createBooleanParser();\n break;\n case 'serial':\n case 'int':\n case 'bigint':\n parsers[field.name as keyof T] = createIntegerParser();\n break;\n case 'timestamptz':\n parsers[field.name as keyof T] = createTimestampParser();\n break;\n default:\n break;\n }\n }\n\n return parsers;\n}\n","import { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../context/index';\n\nexport type APIViewMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\ntype APIViewMethodHandler = (ctx: RequestContext) => Promise<TangoResponse>;\n\n/**\n * Lightweight class-based request dispatcher for non-model API endpoints.\n */\nexport abstract class APIView {\n static readonly BRAND = 'tango.resources.api_view' as const;\n readonly __tangoBrand: typeof APIView.BRAND = APIView.BRAND;\n\n /**\n * Narrow an unknown value to `APIView`.\n */\n static isAPIView(value: unknown): value is APIView {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === APIView.BRAND\n );\n }\n\n /**\n * Dispatch the request to the handler for the current HTTP method.\n */\n async dispatch(ctx: RequestContext): Promise<TangoResponse> {\n const method = normalizeMethod(ctx.request.method);\n if (!method) {\n return this.httpMethodNotAllowed();\n }\n\n const handler = this.getMethodHandler(method);\n return handler(ctx);\n }\n\n getAllowedMethods(): readonly APIViewMethod[] {\n const allowed: APIViewMethod[] = [];\n if (this.get !== APIView.prototype.get) {\n allowed.push('GET');\n }\n if (this.post !== APIView.prototype.post) {\n allowed.push('POST');\n }\n if (this.put !== APIView.prototype.put) {\n allowed.push('PUT');\n }\n if (this.patch !== APIView.prototype.patch) {\n allowed.push('PATCH');\n }\n if (this.delete !== APIView.prototype.delete) {\n allowed.push('DELETE');\n }\n return allowed;\n }\n\n protected get(_ctx: RequestContext): Promise<TangoResponse> {\n return Promise.resolve(this.httpMethodNotAllowed());\n }\n\n protected post(_ctx: RequestContext): Promise<TangoResponse> {\n return Promise.resolve(this.httpMethodNotAllowed());\n }\n\n protected put(_ctx: RequestContext): Promise<TangoResponse> {\n return Promise.resolve(this.httpMethodNotAllowed());\n }\n\n protected patch(_ctx: RequestContext): Promise<TangoResponse> {\n return Promise.resolve(this.httpMethodNotAllowed());\n }\n\n protected delete(_ctx: RequestContext): Promise<TangoResponse> {\n return Promise.resolve(this.httpMethodNotAllowed());\n }\n\n protected httpMethodNotAllowed(): TangoResponse {\n return TangoResponse.methodNotAllowed(this.getAllowedMethods());\n }\n\n private getMethodHandler(method: APIViewMethod): APIViewMethodHandler {\n if (method === 'GET') {\n return (ctx) => this.get(ctx);\n }\n if (method === 'POST') {\n return (ctx) => this.post(ctx);\n }\n if (method === 'PUT') {\n return (ctx) => this.put(ctx);\n }\n if (method === 'PATCH') {\n return (ctx) => this.patch(ctx);\n }\n return (ctx) => this.delete(ctx);\n }\n}\n\nfunction normalizeMethod(method: string): APIViewMethod | null {\n const upper = method.toUpperCase();\n if (upper === 'GET') {\n return 'GET';\n }\n if (upper === 'POST') {\n return 'POST';\n }\n if (upper === 'PUT') {\n return 'PUT';\n }\n if (upper === 'PATCH') {\n return 'PATCH';\n }\n if (upper === 'DELETE') {\n return 'DELETE';\n }\n return null;\n}\n","import { HttpErrorFactory, TangoResponse, type JsonValue, NotFoundError } from '@danceroutine/tango-core';\nimport { Q, type FilterInput, type ManagerLike, type QuerySet } from '@danceroutine/tango-orm';\nimport type { OffsetPaginatedResponse, Paginator } from '../pagination/index';\nimport { OffsetPaginator } from '../paginators/OffsetPaginator';\nimport { APIView } from './APIView';\nimport { RequestContext } from '../context/index';\nimport type { FilterSet } from '../filters/index';\nimport { inferModelFieldParsers } from '../filters/inferModelFieldParsers';\nimport type { GenericAPIViewOpenAPIDescription } from '../resource/index';\nimport type { ModelSerializerClass, SerializerOutput, SerializerSchema } from '../serializer/index';\nimport type { ResourceModelLike } from '../resource/index';\n\nexport interface GenericAPIViewConfig<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> {\n serializer: TSerializer;\n filters?: FilterSet<TModel>;\n orderingFields?: (keyof TModel)[];\n searchFields?: (keyof TModel)[];\n lookupField?: keyof TModel;\n lookupParam?: string;\n paginatorFactory?: (queryset: QuerySet<TModel>) => Paginator<TModel, SerializerOutput<TSerializer>>;\n}\n\n/**\n * Generic API base class that centralizes query/build/validation helpers.\n */\nexport abstract class GenericAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends APIView {\n protected readonly serializerClass: TSerializer;\n protected readonly filters?: FilterSet<TModel>;\n protected readonly orderingFields: readonly (keyof TModel)[];\n protected readonly searchFields: readonly (keyof TModel)[];\n protected readonly lookupField?: keyof TModel;\n protected readonly lookupParam: string;\n protected readonly paginatorFactory?: (\n queryset: QuerySet<TModel>\n ) => Paginator<TModel, SerializerOutput<TSerializer>>;\n private serializer?: InstanceType<TSerializer>;\n\n constructor(config: GenericAPIViewConfig<TModel, TSerializer>) {\n super();\n this.serializerClass = config.serializer;\n this.filters = config.filters;\n this.orderingFields = config.orderingFields ?? [];\n this.searchFields = config.searchFields ?? [];\n this.lookupField = config.lookupField;\n this.lookupParam = config.lookupParam ?? 'id';\n this.paginatorFactory = config.paginatorFactory;\n }\n\n /**\n * Return the serializer class that owns this resource contract.\n */\n getSerializerClass(): TSerializer {\n return this.serializerClass;\n }\n\n /**\n * Return the serializer instance for the current resource.\n */\n getSerializer(): InstanceType<TSerializer> {\n if (!this.serializer) {\n this.serializer = new this.serializerClass() as InstanceType<TSerializer>;\n }\n\n return this.serializer;\n }\n\n /**\n * Describe the public HTTP contract that this resource contributes to OpenAPI generation.\n */\n describeOpenAPI(): GenericAPIViewOpenAPIDescription<TModel, TSerializer> {\n const model = this.requireModelMetadata();\n return {\n model,\n outputSchema: this.getOutputSchema(),\n createSchema: this.getCreateSchema(),\n updateSchema: this.getUpdateSchema(),\n searchFields: this.searchFields,\n orderingFields: this.orderingFields,\n lookupField: this.lookupField ?? this.getLookupFieldFromMetadata(model),\n lookupParam: this.lookupParam,\n allowedMethods: this.getAllowedMethods(),\n usesDefaultOffsetPagination: !this.paginatorFactory,\n };\n }\n\n protected getManager(): ManagerLike<TModel> {\n return this.getSerializer().getManager();\n }\n\n protected getOutputSchema(): TSerializer['outputSchema'] {\n return this.getSerializer().getOutputSchema() as TSerializer['outputSchema'];\n }\n\n protected getCreateSchema(): TSerializer['createSchema'] {\n return this.getSerializer().getCreateSchema() as TSerializer['createSchema'];\n }\n\n protected getUpdateSchema(): TSerializer['updateSchema'] {\n return this.getSerializer().getUpdateSchema() as TSerializer['updateSchema'];\n }\n\n protected getLookupField(): keyof TModel {\n return this.lookupField ?? (this.getManager().meta.pk as keyof TModel);\n }\n\n protected getLookupValue(ctx: RequestContext): string | null {\n const value = ctx.params[this.lookupParam]?.trim();\n return value || null;\n }\n\n protected getPaginator(queryset: QuerySet<TModel>): Paginator<TModel, SerializerOutput<TSerializer>> {\n if (this.paginatorFactory) {\n return this.paginatorFactory(queryset);\n }\n return new OffsetPaginator<TModel>(queryset) as Paginator<TModel, SerializerOutput<TSerializer>>;\n }\n\n protected async performList(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const params = ctx.request.queryParams;\n const baseQueryset = this.getManager().query();\n const paginator = this.getPaginator(baseQueryset);\n paginator.parse(params);\n\n let qs = baseQueryset;\n\n if (this.filters) {\n const filterInputs = this.filters\n .withFieldParsers(inferModelFieldParsers(this.getSerializer().getModel()))\n .apply(params);\n if (filterInputs.length > 0) {\n qs = qs.filter(Q.and(...filterInputs));\n }\n }\n\n const search = params.getSearch();\n if (search && this.searchFields.length > 0) {\n const searchFilters: FilterInput<TModel>[] = this.searchFields.map((field) => {\n const lookup = `${String(field)}__icontains`;\n return { [lookup]: search } as FilterInput<TModel>;\n });\n qs = qs.filter(Q.or(...searchFilters));\n }\n\n const ordering = params.getOrdering();\n if (ordering.length > 0) {\n const orderTokens = ordering.filter((field) => {\n const cleanField = field.startsWith('-') ? field.slice(1) : field;\n return this.orderingFields.includes(cleanField as keyof TModel);\n });\n if (orderTokens.length > 0) {\n qs = qs.orderBy(...orderTokens.map((token) => token as keyof TModel | `-${string & keyof TModel}`));\n }\n }\n\n qs = paginator.apply(qs);\n const resultPromise = qs.fetch();\n const totalCountPromise = paginator.needsTotalCount()\n ? qs.count()\n : Promise.resolve<number | undefined>(undefined);\n const [result, totalCount] = await Promise.all([resultPromise, totalCountPromise]);\n const serializer = this.getSerializer();\n const response = paginator.toResponse(\n result.results.map((row) => serializer.toRepresentation(row)) as SerializerOutput<TSerializer>[],\n { totalCount }\n ) as OffsetPaginatedResponse<SerializerOutput<TSerializer>>;\n\n return TangoResponse.json(response as unknown as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected async performCreate(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const body = await ctx.request.json();\n const result = await this.getSerializer().create(body);\n\n return TangoResponse.created(undefined, result as JsonValue);\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected async performRetrieve(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const value = this.getLookupValue(ctx);\n if (!value) {\n throw new NotFoundError('Lookup parameter was not provided.');\n }\n\n const lookupField = this.getLookupField();\n const filterByLookup = { [lookupField]: value } as FilterInput<TModel>;\n const result = await this.getManager().query().filter(filterByLookup).fetchOne();\n if (!result) {\n throw new NotFoundError(\n `No ${this.getManager().meta.table} record found for ${String(lookupField)}=${value}.`\n );\n }\n\n return TangoResponse.json(this.getSerializer().toRepresentation(result) as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected async performUpdate(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const value = this.getLookupValue(ctx);\n if (!value) {\n throw new NotFoundError('Lookup parameter was not provided.');\n }\n\n const body = await ctx.request.json();\n const result = await this.getSerializer().update(value as TModel[keyof TModel], body);\n\n return TangoResponse.json(result as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected async performDestroy(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const value = this.getLookupValue(ctx);\n if (!value) {\n throw new NotFoundError('Lookup parameter was not provided.');\n }\n\n await this.getManager().delete(value as TModel[keyof TModel]);\n return TangoResponse.noContent();\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected handleError(error: unknown): TangoResponse {\n const httpError = HttpErrorFactory.toHttpError(error);\n return TangoResponse.json(httpError.body as JsonValue, { status: httpError.status });\n }\n\n private requireModelMetadata(): ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n } {\n const model = this.getSerializer().getModel();\n\n if (!model.metadata) {\n throw new Error('OpenAPI generation requires Tango model metadata on GenericAPIView models.');\n }\n\n return model as ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n };\n }\n\n private getLookupFieldFromMetadata(\n model: ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n }\n ): keyof TModel {\n const primaryKeyField = model.metadata.fields.find((field) => field.primaryKey);\n\n if (!primaryKeyField) {\n throw new Error('OpenAPI generation requires a primary key field in Tango model metadata.');\n }\n\n return primaryKeyField.name as keyof TModel;\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Mixin that wires `GET` requests to the generic list implementation.\n */\nexport abstract class ListModelMixin<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected list(ctx: RequestContext): Promise<TangoResponse> {\n return this.performList(ctx);\n }\n\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.list(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Mixin that wires `POST` requests to the generic create implementation.\n */\nexport abstract class CreateModelMixin<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected create(ctx: RequestContext): Promise<TangoResponse> {\n return this.performCreate(ctx);\n }\n\n protected override post(ctx: RequestContext): Promise<TangoResponse> {\n return this.create(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Mixin that wires `GET` requests to the generic retrieve implementation.\n */\nexport abstract class RetrieveModelMixin<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected retrieve(ctx: RequestContext): Promise<TangoResponse> {\n return this.performRetrieve(ctx);\n }\n\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.retrieve(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Mixin that wires `PUT` and `PATCH` requests to the generic update implementation.\n */\nexport abstract class UpdateModelMixin<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected update(ctx: RequestContext): Promise<TangoResponse> {\n return this.performUpdate(ctx);\n }\n\n protected override put(ctx: RequestContext): Promise<TangoResponse> {\n return this.update(ctx);\n }\n\n protected override patch(ctx: RequestContext): Promise<TangoResponse> {\n return this.update(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Mixin that wires `DELETE` requests to the generic destroy implementation.\n */\nexport abstract class DestroyModelMixin<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected destroy(ctx: RequestContext): Promise<TangoResponse> {\n return this.performDestroy(ctx);\n }\n\n protected override delete(ctx: RequestContext): Promise<TangoResponse> {\n return this.destroy(ctx);\n }\n}\n","import { ListModelMixin } from '../mixins/ListModelMixin';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for endpoints that only expose a list operation.\n */\nexport abstract class ListAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends ListModelMixin<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return super.get(ctx);\n }\n}\n","import { CreateModelMixin } from '../mixins/CreateModelMixin';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for endpoints that only support resource creation.\n */\nexport abstract class CreateAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends CreateModelMixin<TModel, TSerializer> {\n protected override post(ctx: RequestContext): Promise<TangoResponse> {\n return super.post(ctx);\n }\n}\n","import { RetrieveModelMixin } from '../mixins/RetrieveModelMixin';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for endpoints that retrieve a single resource by lookup.\n */\nexport abstract class RetrieveAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends RetrieveModelMixin<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return super.get(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for collection endpoints that list and create resources.\n */\nexport abstract class ListCreateAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.performList(ctx);\n }\n\n protected override post(ctx: RequestContext): Promise<TangoResponse> {\n return this.performCreate(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for endpoints that retrieve and update a single resource.\n */\nexport abstract class RetrieveUpdateAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.performRetrieve(ctx);\n }\n\n protected override put(ctx: RequestContext): Promise<TangoResponse> {\n return this.performUpdate(ctx);\n }\n\n protected override patch(ctx: RequestContext): Promise<TangoResponse> {\n return this.performUpdate(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for endpoints that retrieve and delete a single resource.\n */\nexport abstract class RetrieveDestroyAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.performRetrieve(ctx);\n }\n\n protected override delete(ctx: RequestContext): Promise<TangoResponse> {\n return this.performDestroy(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for full detail endpoints that retrieve, update, and delete.\n */\nexport abstract class RetrieveUpdateDestroyAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.performRetrieve(ctx);\n }\n\n protected override put(ctx: RequestContext): Promise<TangoResponse> {\n return this.performUpdate(ctx);\n }\n\n protected override patch(ctx: RequestContext): Promise<TangoResponse> {\n return this.performUpdate(ctx);\n }\n\n protected override delete(ctx: RequestContext): Promise<TangoResponse> {\n return this.performDestroy(ctx);\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { APIView, type APIViewMethod } from './APIView';\nexport { GenericAPIView, type GenericAPIViewConfig } from './GenericAPIView';\nexport type { GenericAPIViewOpenAPIDescription } from '../resource/index';\nexport {\n ListModelMixin,\n CreateModelMixin,\n RetrieveModelMixin,\n UpdateModelMixin,\n DestroyModelMixin,\n} from './mixins/index';\nexport {\n ListAPIView,\n CreateAPIView,\n RetrieveAPIView,\n ListCreateAPIView,\n RetrieveUpdateAPIView,\n RetrieveDestroyAPIView,\n RetrieveUpdateDestroyAPIView,\n} from './generics/index';\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danceroutine/tango-resources",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "ModelViewSet, serializers, filters, and pagination for Tango",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -58,8 +58,8 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "zod": "^4.0.0",
61
- "@danceroutine/tango-orm": "1.3.0",
62
- "@danceroutine/tango-core": "1.3.0"
61
+ "@danceroutine/tango-core": "1.5.0",
62
+ "@danceroutine/tango-orm": "1.5.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/node": "^22.9.0",
@@ -67,8 +67,8 @@
67
67
  "typescript": "^5.6.3",
68
68
  "vitest": "^4.0.6",
69
69
  "zod": "^4.0.0",
70
- "@danceroutine/tango-schema": "1.3.0",
71
- "@danceroutine/tango-testing": "1.3.0"
70
+ "@danceroutine/tango-testing": "1.5.0",
71
+ "@danceroutine/tango-schema": "1.5.0"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "tsdown",
@@ -1 +0,0 @@
1
- {"version":3,"file":"view-CTogofVY.js","names":["OffsetPaginationInput: z.ZodType<OffsetPaginationInputValue>","results: T[]","pageNumber: number","perPage: number","totalCount?: number","value: unknown","queryset: QuerySet<T>","params: TangoQueryParams","results: TResult[]","context?: { totalCount?: number }","response: OffsetPaginatedResponse<TResult>","page: number","raw: string | string[]","model: ResourceModelLike<T>","parsers: Partial<Record<keyof T, FilterValueParser>>","value: unknown","ctx: RequestContext","allowed: APIViewMethod[]","_ctx: RequestContext","method: APIViewMethod","method: string","config: GenericAPIViewConfig<TModel, TSerializer>","ctx: RequestContext","queryset: QuerySet<TModel>","searchFilters: FilterInput<TModel>[]","error: unknown","model: ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n }","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext","ctx: RequestContext"],"sources":["../src/pagination/OffsetPaginationInput.ts","../src/paginators/OffsetPaginator.ts","../src/filters/inferModelFieldParsers.ts","../src/view/APIView.ts","../src/view/GenericAPIView.ts","../src/view/mixins/ListModelMixin.ts","../src/view/mixins/CreateModelMixin.ts","../src/view/mixins/RetrieveModelMixin.ts","../src/view/mixins/UpdateModelMixin.ts","../src/view/mixins/DestroyModelMixin.ts","../src/view/generics/ListAPIView.ts","../src/view/generics/CreateAPIView.ts","../src/view/generics/RetrieveAPIView.ts","../src/view/generics/ListCreateAPIView.ts","../src/view/generics/RetrieveUpdateAPIView.ts","../src/view/generics/RetrieveDestroyAPIView.ts","../src/view/generics/RetrieveUpdateDestroyAPIView.ts","../src/view/index.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport type OffsetPaginationInputValue = {\n limit: number;\n offset: number;\n page?: number;\n};\n\nexport const OffsetPaginationInput: z.ZodType<OffsetPaginationInputValue> = z.object({\n limit: z.coerce\n .number()\n .int()\n .min(1)\n .default(25)\n .transform((value) => Math.min(value, 100)),\n offset: z.coerce.number().int().min(0).default(0),\n page: z.coerce.number().int().min(1).optional(),\n});\n","import { TangoQueryParams } from '@danceroutine/tango-core';\nimport type { QuerySet } from '@danceroutine/tango-orm';\nimport type { Paginator, Page } from '../pagination/Paginator';\nimport type { OffsetPaginatedResponse } from '../pagination/PaginatedResponse';\nimport { OffsetPaginationInput } from '../pagination/OffsetPaginationInput';\n\nclass OffsetPage<T> implements Page<T> {\n static readonly BRAND = 'tango.resources.offset_page' as const;\n readonly __tangoBrand: typeof OffsetPage.BRAND = OffsetPage.BRAND;\n\n constructor(\n public readonly results: T[],\n private readonly pageNumber: number,\n private readonly perPage: number,\n private readonly totalCount?: number\n ) {}\n\n static isOffsetPage<T>(value: unknown): value is OffsetPage<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === OffsetPage.BRAND\n );\n }\n\n /** Whether a next page exists based on known total count. */\n hasNext(): boolean {\n if (this.totalCount === undefined) {\n return false;\n }\n return this.endIndex() < this.totalCount;\n }\n\n /** Whether a previous page exists. */\n hasPrevious(): boolean {\n return this.pageNumber > 1;\n }\n\n /** The next page number, if available. */\n nextPageNumber(): number | null {\n return this.hasNext() ? this.pageNumber + 1 : null;\n }\n\n /** The previous page number, if available. */\n previousPageNumber(): number | null {\n return this.hasPrevious() ? this.pageNumber - 1 : null;\n }\n\n /** Zero-based start index of this page in the full result set. */\n startIndex(): number {\n return (this.pageNumber - 1) * this.perPage;\n }\n\n /** Exclusive end index of this page in the full result set. */\n endIndex(): number {\n return this.startIndex() + this.results.length;\n }\n}\n\n/**\n * Offset/limit paginator modelled after DRF's LimitOffsetPagination.\n * Handles parsing limit/offset/page from URL query params and building\n * the paginated response envelope with next/previous links.\n *\n * @example\n * ```typescript\n * const paginator = new OffsetPaginator(queryset);\n * const { limit, offset } = paginator.parseParams(searchParams);\n * const results = await queryset.limit(limit).offset(offset).fetchAll();\n * const response = paginator.getPaginatedResponse(results, totalCount);\n * ```\n */\nexport class OffsetPaginator<T extends Record<string, unknown>> implements Paginator<T, T, OffsetPaginatedResponse<T>> {\n static readonly BRAND = 'tango.resources.offset_paginator' as const;\n readonly __tangoBrand: typeof OffsetPaginator.BRAND = OffsetPaginator.BRAND;\n private limit = 25;\n private offset = 0;\n\n constructor(\n private queryset: QuerySet<T>,\n private perPage: number = 25\n ) {\n this.limit = perPage;\n }\n\n /**\n * Narrow an unknown value to `OffsetPaginator`.\n */\n static isOffsetPaginator<T extends Record<string, unknown>>(value: unknown): value is OffsetPaginator<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === OffsetPaginator.BRAND\n );\n }\n\n /**\n * Parse limit, offset, and page from Tango query params.\n * If `page` is provided, it's converted to an offset.\n * Stores parsed values for use by getPaginatedResponse.\n */\n parse(params: TangoQueryParams): void {\n const input = {\n limit: params.get('limit') ?? undefined,\n offset: params.get('offset') ?? undefined,\n page: params.get('page') ?? undefined,\n };\n\n const parsed = OffsetPaginationInput.parse(input);\n\n if (parsed.page) {\n parsed.offset = (parsed.page - 1) * parsed.limit;\n }\n\n this.limit = parsed.limit;\n this.offset = parsed.offset;\n }\n\n /**\n * Parse params and return `{ limit, offset }` for compatibility callers.\n */\n parseParams(params: TangoQueryParams): { limit: number; offset: number } {\n this.parse(params);\n return { limit: this.limit, offset: this.offset };\n }\n\n /**\n * Build a DRF-style paginated response with count, next, and previous links.\n * Uses the limit/offset stored from the most recent parseParams call.\n */\n needsTotalCount(): boolean {\n return true;\n }\n\n toResponse<TResult>(results: TResult[], context?: { totalCount?: number }): OffsetPaginatedResponse<TResult> {\n const totalCount = context?.totalCount;\n const response: OffsetPaginatedResponse<TResult> = { results };\n\n if (totalCount !== undefined) {\n response.count = totalCount;\n\n if (this.offset + this.limit < totalCount) {\n response.next = `?limit=${this.limit}&offset=${this.offset + this.limit}`;\n }\n\n if (this.offset > 0) {\n const prevOffset = Math.max(0, this.offset - this.limit);\n response.previous = `?limit=${this.limit}&offset=${prevOffset}`;\n }\n }\n\n return response;\n }\n\n /**\n * Backward-compatible alias for `toResponse`.\n */\n getPaginatedResponse<TResult>(results: TResult[], totalCount?: number): OffsetPaginatedResponse<TResult> {\n return this.toResponse(results, { totalCount });\n }\n\n /**\n * Apply current limit/offset to a queryset.\n */\n apply(queryset: QuerySet<T>): QuerySet<T> {\n return queryset.limit(this.limit).offset(this.offset);\n }\n\n /**\n * Fetch a 1-based page number from the bound queryset.\n */\n async paginate(page: number): Promise<Page<T>> {\n return this.getPage(page);\n }\n\n /**\n * Fetch a 1-based page and return page metadata.\n */\n async getPage(page: number): Promise<Page<T>> {\n const offset = (page - 1) * this.perPage;\n const results = await this.queryset.offset(offset).limit(this.perPage).fetch();\n\n const totalCount = await this.count();\n\n return new OffsetPage(results.results, page, this.perPage, totalCount);\n }\n\n /**\n * Count total rows for the current queryset state.\n */\n async count(): Promise<number> {\n return this.queryset.count();\n }\n}\n","import type { ResourceModelLike } from '../resource/index';\nimport type { FilterValueParser } from './FilterSet';\n\nfunction normalizeParserTokens(raw: string | string[]): string[] {\n const tokens = Array.isArray(raw) ? raw : String(raw).split(',');\n const normalized = tokens.map((value) => value.trim());\n return normalized.every((value) => value.length > 0) ? normalized : [];\n}\n\nfunction createBooleanParser(): FilterValueParser {\n return (raw) => {\n const values = normalizeParserTokens(raw);\n if (values.length === 0) {\n return undefined;\n }\n\n const parsed = values.map((value) => {\n const normalized = value.toLowerCase();\n\n if (normalized === 'true' || normalized === '1') {\n return true;\n }\n\n if (normalized === 'false' || normalized === '0') {\n return false;\n }\n\n return null;\n });\n\n if (parsed.some((value) => value === null)) {\n return undefined;\n }\n\n return parsed.length === 1 ? parsed[0]! : (parsed as boolean[]);\n };\n}\n\nfunction createIntegerParser(): FilterValueParser {\n return (raw) => {\n const values = normalizeParserTokens(raw);\n if (values.length === 0) {\n return undefined;\n }\n\n const parsed = values.map(Number);\n\n if (parsed.some((value) => !Number.isInteger(value))) {\n return undefined;\n }\n\n return parsed.length === 1 ? parsed[0] : parsed;\n };\n}\n\nfunction createTimestampParser(): FilterValueParser {\n return (raw) => {\n const values = normalizeParserTokens(raw);\n if (values.length === 0) {\n return undefined;\n }\n\n const parsed = values.map((value) => {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n });\n\n if (parsed.some((value) => value === null)) {\n return undefined;\n }\n\n return parsed.length === 1 ? parsed[0]! : (parsed as Date[]);\n };\n}\n\n/**\n * Infer resource-level query-value parsers from Tango model metadata.\n *\n * Parsers are inferred conservatively from field metadata so HTTP query filters\n * can be coerced into typed ORM inputs without framework-specific glue.\n */\nexport function inferModelFieldParsers<T extends Record<string, unknown>>(\n model: ResourceModelLike<T>\n): Partial<Record<keyof T, FilterValueParser>> {\n const metadata = model.metadata;\n if (!metadata) {\n return {};\n }\n\n const parsers: Partial<Record<keyof T, FilterValueParser>> = {};\n\n for (const field of metadata.fields) {\n switch (field.type) {\n case 'bool':\n parsers[field.name as keyof T] = createBooleanParser();\n break;\n case 'serial':\n case 'int':\n case 'bigint':\n parsers[field.name as keyof T] = createIntegerParser();\n break;\n case 'timestamptz':\n parsers[field.name as keyof T] = createTimestampParser();\n break;\n default:\n break;\n }\n }\n\n return parsers;\n}\n","import { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../context/index';\n\nexport type APIViewMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\ntype APIViewMethodHandler = (ctx: RequestContext) => Promise<TangoResponse>;\n\n/**\n * Lightweight class-based request dispatcher for non-model API endpoints.\n */\nexport abstract class APIView {\n static readonly BRAND = 'tango.resources.api_view' as const;\n readonly __tangoBrand: typeof APIView.BRAND = APIView.BRAND;\n\n /**\n * Narrow an unknown value to `APIView`.\n */\n static isAPIView(value: unknown): value is APIView {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === APIView.BRAND\n );\n }\n\n /**\n * Dispatch the request to the handler for the current HTTP method.\n */\n async dispatch(ctx: RequestContext): Promise<TangoResponse> {\n const method = normalizeMethod(ctx.request.method);\n if (!method) {\n return this.httpMethodNotAllowed();\n }\n\n const handler = this.getMethodHandler(method);\n return handler(ctx);\n }\n\n getAllowedMethods(): readonly APIViewMethod[] {\n const allowed: APIViewMethod[] = [];\n if (this.get !== APIView.prototype.get) {\n allowed.push('GET');\n }\n if (this.post !== APIView.prototype.post) {\n allowed.push('POST');\n }\n if (this.put !== APIView.prototype.put) {\n allowed.push('PUT');\n }\n if (this.patch !== APIView.prototype.patch) {\n allowed.push('PATCH');\n }\n if (this.delete !== APIView.prototype.delete) {\n allowed.push('DELETE');\n }\n return allowed;\n }\n\n protected get(_ctx: RequestContext): Promise<TangoResponse> {\n return Promise.resolve(this.httpMethodNotAllowed());\n }\n\n protected post(_ctx: RequestContext): Promise<TangoResponse> {\n return Promise.resolve(this.httpMethodNotAllowed());\n }\n\n protected put(_ctx: RequestContext): Promise<TangoResponse> {\n return Promise.resolve(this.httpMethodNotAllowed());\n }\n\n protected patch(_ctx: RequestContext): Promise<TangoResponse> {\n return Promise.resolve(this.httpMethodNotAllowed());\n }\n\n protected delete(_ctx: RequestContext): Promise<TangoResponse> {\n return Promise.resolve(this.httpMethodNotAllowed());\n }\n\n protected httpMethodNotAllowed(): TangoResponse {\n return TangoResponse.methodNotAllowed(this.getAllowedMethods());\n }\n\n private getMethodHandler(method: APIViewMethod): APIViewMethodHandler {\n if (method === 'GET') {\n return (ctx) => this.get(ctx);\n }\n if (method === 'POST') {\n return (ctx) => this.post(ctx);\n }\n if (method === 'PUT') {\n return (ctx) => this.put(ctx);\n }\n if (method === 'PATCH') {\n return (ctx) => this.patch(ctx);\n }\n return (ctx) => this.delete(ctx);\n }\n}\n\nfunction normalizeMethod(method: string): APIViewMethod | null {\n const upper = method.toUpperCase();\n if (upper === 'GET') {\n return 'GET';\n }\n if (upper === 'POST') {\n return 'POST';\n }\n if (upper === 'PUT') {\n return 'PUT';\n }\n if (upper === 'PATCH') {\n return 'PATCH';\n }\n if (upper === 'DELETE') {\n return 'DELETE';\n }\n return null;\n}\n","import { HttpErrorFactory, TangoResponse, type JsonValue, NotFoundError } from '@danceroutine/tango-core';\nimport { Q, type FilterInput, type ManagerLike, type QuerySet } from '@danceroutine/tango-orm';\nimport type { OffsetPaginatedResponse, Paginator } from '../pagination/index';\nimport { OffsetPaginator } from '../paginators/OffsetPaginator';\nimport { APIView } from './APIView';\nimport { RequestContext } from '../context/index';\nimport type { FilterSet } from '../filters/index';\nimport { inferModelFieldParsers } from '../filters/inferModelFieldParsers';\nimport type { GenericAPIViewOpenAPIDescription } from '../resource/index';\nimport type { ModelSerializerClass, SerializerOutput, SerializerSchema } from '../serializer/index';\nimport type { ResourceModelLike } from '../resource/index';\n\nexport interface GenericAPIViewConfig<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> {\n serializer: TSerializer;\n filters?: FilterSet<TModel>;\n orderingFields?: (keyof TModel)[];\n searchFields?: (keyof TModel)[];\n lookupField?: keyof TModel;\n lookupParam?: string;\n paginatorFactory?: (queryset: QuerySet<TModel>) => Paginator<TModel, SerializerOutput<TSerializer>>;\n}\n\n/**\n * Generic API base class that centralizes query/build/validation helpers.\n */\nexport abstract class GenericAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends APIView {\n protected readonly serializerClass: TSerializer;\n protected readonly filters?: FilterSet<TModel>;\n protected readonly orderingFields: readonly (keyof TModel)[];\n protected readonly searchFields: readonly (keyof TModel)[];\n protected readonly lookupField?: keyof TModel;\n protected readonly lookupParam: string;\n protected readonly paginatorFactory?: (\n queryset: QuerySet<TModel>\n ) => Paginator<TModel, SerializerOutput<TSerializer>>;\n private serializer?: InstanceType<TSerializer>;\n\n constructor(config: GenericAPIViewConfig<TModel, TSerializer>) {\n super();\n this.serializerClass = config.serializer;\n this.filters = config.filters;\n this.orderingFields = config.orderingFields ?? [];\n this.searchFields = config.searchFields ?? [];\n this.lookupField = config.lookupField;\n this.lookupParam = config.lookupParam ?? 'id';\n this.paginatorFactory = config.paginatorFactory;\n }\n\n /**\n * Return the serializer class that owns this resource contract.\n */\n getSerializerClass(): TSerializer {\n return this.serializerClass;\n }\n\n /**\n * Return the serializer instance for the current resource.\n */\n getSerializer(): InstanceType<TSerializer> {\n if (!this.serializer) {\n this.serializer = new this.serializerClass() as InstanceType<TSerializer>;\n }\n\n return this.serializer;\n }\n\n /**\n * Describe the public HTTP contract that this resource contributes to OpenAPI generation.\n */\n describeOpenAPI(): GenericAPIViewOpenAPIDescription<TModel, TSerializer> {\n const model = this.requireModelMetadata();\n return {\n model,\n outputSchema: this.getOutputSchema(),\n createSchema: this.getCreateSchema(),\n updateSchema: this.getUpdateSchema(),\n searchFields: this.searchFields,\n orderingFields: this.orderingFields,\n lookupField: this.lookupField ?? this.getLookupFieldFromMetadata(model),\n lookupParam: this.lookupParam,\n allowedMethods: this.getAllowedMethods(),\n usesDefaultOffsetPagination: !this.paginatorFactory,\n };\n }\n\n protected getManager(): ManagerLike<TModel> {\n return this.getSerializer().getManager();\n }\n\n protected getOutputSchema(): TSerializer['outputSchema'] {\n return this.getSerializer().getOutputSchema() as TSerializer['outputSchema'];\n }\n\n protected getCreateSchema(): TSerializer['createSchema'] {\n return this.getSerializer().getCreateSchema() as TSerializer['createSchema'];\n }\n\n protected getUpdateSchema(): TSerializer['updateSchema'] {\n return this.getSerializer().getUpdateSchema() as TSerializer['updateSchema'];\n }\n\n protected getLookupField(): keyof TModel {\n return this.lookupField ?? (this.getManager().meta.pk as keyof TModel);\n }\n\n protected getLookupValue(ctx: RequestContext): string | null {\n const value = ctx.params[this.lookupParam]?.trim();\n return value || null;\n }\n\n protected getPaginator(queryset: QuerySet<TModel>): Paginator<TModel, SerializerOutput<TSerializer>> {\n if (this.paginatorFactory) {\n return this.paginatorFactory(queryset);\n }\n return new OffsetPaginator<TModel>(queryset) as Paginator<TModel, SerializerOutput<TSerializer>>;\n }\n\n protected async performList(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const params = ctx.request.queryParams;\n const baseQueryset = this.getManager().query();\n const paginator = this.getPaginator(baseQueryset);\n paginator.parse(params);\n\n let qs = baseQueryset;\n\n if (this.filters) {\n const filterInputs = this.filters\n .withFieldParsers(inferModelFieldParsers(this.getSerializer().getModel()))\n .apply(params);\n if (filterInputs.length > 0) {\n qs = qs.filter(Q.and(...filterInputs));\n }\n }\n\n const search = params.getSearch();\n if (search && this.searchFields.length > 0) {\n const searchFilters: FilterInput<TModel>[] = this.searchFields.map((field) => {\n const lookup = `${String(field)}__icontains`;\n return { [lookup]: search } as FilterInput<TModel>;\n });\n qs = qs.filter(Q.or(...searchFilters));\n }\n\n const ordering = params.getOrdering();\n if (ordering.length > 0) {\n const orderTokens = ordering.filter((field) => {\n const cleanField = field.startsWith('-') ? field.slice(1) : field;\n return this.orderingFields.includes(cleanField as keyof TModel);\n });\n if (orderTokens.length > 0) {\n qs = qs.orderBy(...orderTokens.map((token) => token as keyof TModel | `-${string & keyof TModel}`));\n }\n }\n\n qs = paginator.apply(qs);\n const resultPromise = qs.fetch();\n const totalCountPromise = paginator.needsTotalCount()\n ? qs.count()\n : Promise.resolve<number | undefined>(undefined);\n const [result, totalCount] = await Promise.all([resultPromise, totalCountPromise]);\n const serializer = this.getSerializer();\n const response = paginator.toResponse(\n result.results.map((row) => serializer.toRepresentation(row)) as SerializerOutput<TSerializer>[],\n { totalCount }\n ) as OffsetPaginatedResponse<SerializerOutput<TSerializer>>;\n\n return TangoResponse.json(response as unknown as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected async performCreate(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const body = await ctx.request.json();\n const result = await this.getSerializer().create(body);\n\n return TangoResponse.created(undefined, result as JsonValue);\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected async performRetrieve(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const value = this.getLookupValue(ctx);\n if (!value) {\n throw new NotFoundError('Lookup parameter was not provided.');\n }\n\n const lookupField = this.getLookupField();\n const filterByLookup = { [lookupField]: value } as FilterInput<TModel>;\n const result = await this.getManager().query().filter(filterByLookup).fetchOne();\n if (!result) {\n throw new NotFoundError(\n `No ${this.getManager().meta.table} record found for ${String(lookupField)}=${value}.`\n );\n }\n\n return TangoResponse.json(this.getSerializer().toRepresentation(result) as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected async performUpdate(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const value = this.getLookupValue(ctx);\n if (!value) {\n throw new NotFoundError('Lookup parameter was not provided.');\n }\n\n const body = await ctx.request.json();\n const result = await this.getSerializer().update(value as TModel[keyof TModel], body);\n\n return TangoResponse.json(result as JsonValue, { status: 200 });\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected async performDestroy(ctx: RequestContext): Promise<TangoResponse> {\n try {\n const value = this.getLookupValue(ctx);\n if (!value) {\n throw new NotFoundError('Lookup parameter was not provided.');\n }\n\n await this.getManager().delete(value as TModel[keyof TModel]);\n return TangoResponse.noContent();\n } catch (error) {\n return this.handleError(error);\n }\n }\n\n protected handleError(error: unknown): TangoResponse {\n const httpError = HttpErrorFactory.toHttpError(error);\n return TangoResponse.json(httpError.body as JsonValue, { status: httpError.status });\n }\n\n private requireModelMetadata(): ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n } {\n const model = this.getSerializer().getModel();\n\n if (!model.metadata) {\n throw new Error('OpenAPI generation requires Tango model metadata on GenericAPIView models.');\n }\n\n return model as ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n };\n }\n\n private getLookupFieldFromMetadata(\n model: ResourceModelLike<TModel> & {\n metadata: NonNullable<ResourceModelLike<TModel>['metadata']>;\n }\n ): keyof TModel {\n const primaryKeyField = model.metadata.fields.find((field) => field.primaryKey);\n\n if (!primaryKeyField) {\n throw new Error('OpenAPI generation requires a primary key field in Tango model metadata.');\n }\n\n return primaryKeyField.name as keyof TModel;\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Mixin that wires `GET` requests to the generic list implementation.\n */\nexport abstract class ListModelMixin<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected list(ctx: RequestContext): Promise<TangoResponse> {\n return this.performList(ctx);\n }\n\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.list(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Mixin that wires `POST` requests to the generic create implementation.\n */\nexport abstract class CreateModelMixin<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected create(ctx: RequestContext): Promise<TangoResponse> {\n return this.performCreate(ctx);\n }\n\n protected override post(ctx: RequestContext): Promise<TangoResponse> {\n return this.create(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Mixin that wires `GET` requests to the generic retrieve implementation.\n */\nexport abstract class RetrieveModelMixin<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected retrieve(ctx: RequestContext): Promise<TangoResponse> {\n return this.performRetrieve(ctx);\n }\n\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.retrieve(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Mixin that wires `PUT` and `PATCH` requests to the generic update implementation.\n */\nexport abstract class UpdateModelMixin<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected update(ctx: RequestContext): Promise<TangoResponse> {\n return this.performUpdate(ctx);\n }\n\n protected override put(ctx: RequestContext): Promise<TangoResponse> {\n return this.update(ctx);\n }\n\n protected override patch(ctx: RequestContext): Promise<TangoResponse> {\n return this.update(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Mixin that wires `DELETE` requests to the generic destroy implementation.\n */\nexport abstract class DestroyModelMixin<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected destroy(ctx: RequestContext): Promise<TangoResponse> {\n return this.performDestroy(ctx);\n }\n\n protected override delete(ctx: RequestContext): Promise<TangoResponse> {\n return this.destroy(ctx);\n }\n}\n","import { ListModelMixin } from '../mixins/ListModelMixin';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for endpoints that only expose a list operation.\n */\nexport abstract class ListAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends ListModelMixin<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return super.get(ctx);\n }\n}\n","import { CreateModelMixin } from '../mixins/CreateModelMixin';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for endpoints that only support resource creation.\n */\nexport abstract class CreateAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends CreateModelMixin<TModel, TSerializer> {\n protected override post(ctx: RequestContext): Promise<TangoResponse> {\n return super.post(ctx);\n }\n}\n","import { RetrieveModelMixin } from '../mixins/RetrieveModelMixin';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for endpoints that retrieve a single resource by lookup.\n */\nexport abstract class RetrieveAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends RetrieveModelMixin<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return super.get(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for collection endpoints that list and create resources.\n */\nexport abstract class ListCreateAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.performList(ctx);\n }\n\n protected override post(ctx: RequestContext): Promise<TangoResponse> {\n return this.performCreate(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for endpoints that retrieve and update a single resource.\n */\nexport abstract class RetrieveUpdateAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.performRetrieve(ctx);\n }\n\n protected override put(ctx: RequestContext): Promise<TangoResponse> {\n return this.performUpdate(ctx);\n }\n\n protected override patch(ctx: RequestContext): Promise<TangoResponse> {\n return this.performUpdate(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for endpoints that retrieve and delete a single resource.\n */\nexport abstract class RetrieveDestroyAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.performRetrieve(ctx);\n }\n\n protected override delete(ctx: RequestContext): Promise<TangoResponse> {\n return this.performDestroy(ctx);\n }\n}\n","import { GenericAPIView } from '../GenericAPIView';\nimport type { TangoResponse } from '@danceroutine/tango-core';\nimport { RequestContext } from '../../context/index';\nimport type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';\n\n/**\n * Generic API view for full detail endpoints that retrieve, update, and delete.\n */\nexport abstract class RetrieveUpdateDestroyAPIView<\n TModel extends Record<string, unknown>,\n TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>,\n> extends GenericAPIView<TModel, TSerializer> {\n protected override get(ctx: RequestContext): Promise<TangoResponse> {\n return this.performRetrieve(ctx);\n }\n\n protected override put(ctx: RequestContext): Promise<TangoResponse> {\n return this.performUpdate(ctx);\n }\n\n protected override patch(ctx: RequestContext): Promise<TangoResponse> {\n return this.performUpdate(ctx);\n }\n\n protected override delete(ctx: RequestContext): Promise<TangoResponse> {\n return this.performDestroy(ctx);\n }\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { APIView, type APIViewMethod } from './APIView';\nexport { GenericAPIView, type GenericAPIViewConfig } from './GenericAPIView';\nexport type { GenericAPIViewOpenAPIDescription } from '../resource/index';\nexport {\n ListModelMixin,\n CreateModelMixin,\n RetrieveModelMixin,\n UpdateModelMixin,\n DestroyModelMixin,\n} from './mixins/index';\nexport {\n ListAPIView,\n CreateAPIView,\n RetrieveAPIView,\n ListCreateAPIView,\n RetrieveUpdateAPIView,\n RetrieveDestroyAPIView,\n RetrieveUpdateDestroyAPIView,\n} from './generics/index';\n"],"mappings":"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"}