@danceroutine/tango-resources 1.7.0 → 1.8.1

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.
Files changed (31) hide show
  1. package/dist/filters/FilterSet.d.ts +12 -10
  2. package/dist/filters/internal/InternalFilterLookup.d.ts +15 -0
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +229 -21
  5. package/dist/index.js.map +1 -1
  6. package/dist/resource/OpenAPIDescription.d.ts +6 -4
  7. package/dist/resource/ResourceModelLike.d.ts +8 -2
  8. package/dist/serializer/ModelSerializer.d.ts +32 -12
  9. package/dist/serializer/Serializer.d.ts +29 -6
  10. package/dist/serializer/index.d.ts +3 -2
  11. package/dist/serializer/internal/InternalSerializerRelationKind.d.ts +11 -0
  12. package/dist/serializer/relation.d.ts +45 -0
  13. package/dist/view/GenericAPIView.d.ts +7 -5
  14. package/dist/view/generics/CreateAPIView.d.ts +2 -2
  15. package/dist/view/generics/ListAPIView.d.ts +2 -2
  16. package/dist/view/generics/ListCreateAPIView.d.ts +2 -2
  17. package/dist/view/generics/RetrieveAPIView.d.ts +2 -2
  18. package/dist/view/generics/RetrieveDestroyAPIView.d.ts +2 -2
  19. package/dist/view/generics/RetrieveUpdateAPIView.d.ts +2 -2
  20. package/dist/view/generics/RetrieveUpdateDestroyAPIView.d.ts +2 -2
  21. package/dist/view/index.js +1 -1
  22. package/dist/view/mixins/CreateModelMixin.d.ts +2 -2
  23. package/dist/view/mixins/DestroyModelMixin.d.ts +2 -2
  24. package/dist/view/mixins/ListModelMixin.d.ts +2 -2
  25. package/dist/view/mixins/RetrieveModelMixin.d.ts +2 -2
  26. package/dist/view/mixins/UpdateModelMixin.d.ts +2 -2
  27. package/dist/{view-Djm3cQ6C.js → view-iXGdHuS-.js} +4 -3
  28. package/dist/view-iXGdHuS-.js.map +1 -0
  29. package/dist/viewset/ModelViewSet.d.ts +6 -5
  30. package/package.json +5 -5
  31. package/dist/view-Djm3cQ6C.js.map +0 -1
@@ -1,15 +1,18 @@
1
1
  import { z } from 'zod';
2
2
  export type SerializerSchema = z.ZodTypeAny;
3
- export type SerializerClass<TCreateSchema extends SerializerSchema = SerializerSchema, TUpdateSchema extends SerializerSchema = SerializerSchema, TOutputSchema extends SerializerSchema = SerializerSchema> = {
4
- new (): Serializer<TCreateSchema, TUpdateSchema, TOutputSchema>;
3
+ export type SerializerClass<TCreateSchema extends SerializerSchema = SerializerSchema, TUpdateSchema extends SerializerSchema = SerializerSchema, TOutputSchema extends SerializerSchema = SerializerSchema, TRecord = unknown> = {
4
+ new (): Serializer<TCreateSchema, TUpdateSchema, TOutputSchema, TRecord>;
5
5
  readonly createSchema: TCreateSchema;
6
6
  readonly updateSchema: TUpdateSchema;
7
7
  readonly outputSchema: TOutputSchema;
8
+ readonly outputResolvers?: SerializerOutputResolvers<TRecord>;
8
9
  };
9
- export type AnySerializerClass = SerializerClass<SerializerSchema, SerializerSchema, SerializerSchema>;
10
+ export type AnySerializerClass = SerializerClass<SerializerSchema, SerializerSchema, SerializerSchema, any>;
10
11
  export type SerializerCreateInput<TSerializer extends AnySerializerClass> = z.output<TSerializer['createSchema']>;
11
12
  export type SerializerUpdateInput<TSerializer extends AnySerializerClass> = z.output<TSerializer['updateSchema']>;
12
13
  export type SerializerOutput<TSerializer extends AnySerializerClass> = z.output<TSerializer['outputSchema']>;
14
+ export type SerializerOutputResolver<TRecord = unknown> = (record: TRecord) => unknown | Promise<unknown>;
15
+ export type SerializerOutputResolvers<TRecord = unknown> = Record<string, SerializerOutputResolver<TRecord>>;
13
16
  /**
14
17
  * DRF-inspired base serializer backed by Zod schemas.
15
18
  *
@@ -17,14 +20,16 @@ export type SerializerOutput<TSerializer extends AnySerializerClass> = z.output<
17
20
  * inference while centralizing create, update, and representation workflows in
18
21
  * one class-owned contract.
19
22
  */
20
- export declare abstract class Serializer<TCreateSchema extends SerializerSchema, TUpdateSchema extends SerializerSchema, TOutputSchema extends SerializerSchema> {
23
+ export declare abstract class Serializer<TCreateSchema extends SerializerSchema, TUpdateSchema extends SerializerSchema, TOutputSchema extends SerializerSchema, TRecord = unknown> {
21
24
  static readonly createSchema: SerializerSchema;
22
25
  static readonly updateSchema: SerializerSchema;
23
26
  static readonly outputSchema: SerializerSchema;
27
+ static readonly outputResolvers: SerializerOutputResolvers<// oxlint-disable-next-line typescript/no-explicit-any
28
+ any> | undefined;
24
29
  /**
25
30
  * Return the serializer class for the current instance.
26
31
  */
27
- getSerializerClass(): SerializerClass<TCreateSchema, TUpdateSchema, TOutputSchema>;
32
+ getSerializerClass(): SerializerClass<TCreateSchema, TUpdateSchema, TOutputSchema, TRecord>;
28
33
  /**
29
34
  * Return the Zod schema used for create payloads.
30
35
  */
@@ -37,6 +42,11 @@ export declare abstract class Serializer<TCreateSchema extends SerializerSchema,
37
42
  * Return the Zod schema used for serialized output.
38
43
  */
39
44
  getOutputSchema(): TOutputSchema;
45
+ /**
46
+ * Return the resolver map used to enrich serializer output fields before
47
+ * the outward Zod schema parses the final response shape.
48
+ */
49
+ getOutputResolvers(): SerializerOutputResolvers<TRecord>;
40
50
  /**
41
51
  * Validate unknown input for create workflows.
42
52
  */
@@ -47,6 +57,19 @@ export declare abstract class Serializer<TCreateSchema extends SerializerSchema,
47
57
  deserializeUpdate(input: unknown): z.output<TUpdateSchema>;
48
58
  /**
49
59
  * Convert a persisted record into its outward-facing representation.
60
+ *
61
+ * @deprecated Use `serialize(...)` instead so serializer-owned output
62
+ * resolvers run before the outward Zod schema parses the response shape.
63
+ */
64
+ toRepresentation(record: TRecord): z.output<TOutputSchema>;
65
+ /**
66
+ * Resolve serializer-owned output fields and parse the outward response
67
+ * contract.
68
+ */
69
+ serialize(record: TRecord): Promise<z.output<TOutputSchema>>;
70
+ /**
71
+ * Serialize many records through the same outward response contract.
50
72
  */
51
- toRepresentation(record: unknown): z.output<TOutputSchema>;
73
+ serializeMany(records: readonly TRecord[]): Promise<z.output<TOutputSchema>[]>;
74
+ private applyOutputResolvers;
52
75
  }
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Domain boundary barrel: centralizes this subdomain's public contract.
3
3
  */
4
- export { Serializer, type SerializerClass, type AnySerializerClass, type SerializerCreateInput, type SerializerUpdateInput, type SerializerOutput, type SerializerSchema, } from './Serializer';
5
- export { ModelSerializer, type ModelSerializerClass, type AnyModelSerializerClass } from './ModelSerializer';
4
+ export { Serializer, type SerializerClass, type AnySerializerClass, type SerializerCreateInput, type SerializerOutputResolver, type SerializerOutputResolvers, type SerializerUpdateInput, type SerializerOutput, type SerializerSchema, } from './Serializer';
5
+ export { ModelSerializer, type ModelSerializerClass, type AnyModelSerializer, type AnyModelSerializerClass, } from './ModelSerializer';
6
+ export { relation, type ModelSerializerRelationFields, type ManyToManyManagerKeys, type ManyToManyRelationField, type ManyToManyReadStrategy, type ManyToManyWriteStrategy, } from './relation';
@@ -0,0 +1,11 @@
1
+ export declare const InternalSerializerRelationKind: {
2
+ readonly MANY_TO_MANY: "manyToMany";
3
+ };
4
+ export declare const InternalManyToManyReadStrategyKind: {
5
+ readonly PK_LIST: "pkList";
6
+ readonly NESTED: "nested";
7
+ };
8
+ export declare const InternalManyToManyWriteStrategyKind: {
9
+ readonly PK_LIST: "pkList";
10
+ readonly SLUG_LIST: "slugList";
11
+ };
@@ -0,0 +1,45 @@
1
+ import { z } from 'zod';
2
+ import type { ManyToManyRelatedManager } from '@danceroutine/tango-orm';
3
+ import type { ResourceModelLike } from '../resource/ResourceModelLike';
4
+ import { InternalManyToManyReadStrategyKind, InternalManyToManyWriteStrategyKind, InternalSerializerRelationKind } from './internal/InternalSerializerRelationKind';
5
+ type AnyRecord = Record<string, unknown>;
6
+ export type ManyToManyManagerKeys<TRecord extends AnyRecord> = Extract<{
7
+ [K in keyof TRecord]: TRecord[K] extends ManyToManyRelatedManager<AnyRecord> ? K : never;
8
+ }[keyof TRecord], string>;
9
+ export type ManyToManyTargetRow<TRecord extends AnyRecord, TFieldName extends ManyToManyManagerKeys<TRecord>> = TRecord[TFieldName] extends ManyToManyRelatedManager<infer TTarget extends AnyRecord> ? TTarget : never;
10
+ export type ManyToManyPkListStrategy = {
11
+ kind: typeof InternalManyToManyReadStrategyKind.PK_LIST | typeof InternalManyToManyWriteStrategyKind.PK_LIST;
12
+ };
13
+ export type ManyToManyNestedStrategy = {
14
+ kind: typeof InternalManyToManyReadStrategyKind.NESTED;
15
+ schema: z.ZodTypeAny;
16
+ };
17
+ export type ManyToManySlugListStrategy<TTarget extends AnyRecord> = {
18
+ kind: typeof InternalManyToManyWriteStrategyKind.SLUG_LIST;
19
+ model: ResourceModelLike<any, any>;
20
+ lookupField: Extract<keyof TTarget, string>;
21
+ createIfMissing?: boolean;
22
+ buildCreateInput?: (value: string) => Partial<TTarget>;
23
+ };
24
+ export type ManyToManyReadStrategy = ManyToManyPkListStrategy | ManyToManyNestedStrategy;
25
+ export type ManyToManyWriteStrategy<TTarget extends AnyRecord> = ManyToManyPkListStrategy | ManyToManySlugListStrategy<TTarget>;
26
+ export type ManyToManyRelationField<TRecord extends AnyRecord, TFieldName extends ManyToManyManagerKeys<TRecord>> = {
27
+ kind: typeof InternalSerializerRelationKind.MANY_TO_MANY;
28
+ read: ManyToManyReadStrategy;
29
+ write: ManyToManyWriteStrategy<ManyToManyTargetRow<TRecord, TFieldName>>;
30
+ };
31
+ export type ModelSerializerRelationFields<TRecord extends AnyRecord> = Partial<{
32
+ [K in ManyToManyManagerKeys<TRecord>]: ManyToManyRelationField<TRecord, K>;
33
+ }>;
34
+ declare function pkList(): ManyToManyPkListStrategy;
35
+ declare function nested(schema: z.ZodTypeAny): ManyToManyNestedStrategy;
36
+ declare function slugList<TTarget extends AnyRecord>(options: Omit<ManyToManySlugListStrategy<TTarget>, 'kind'>): ManyToManySlugListStrategy<TTarget>;
37
+ declare function manyToMany(config?: Partial<Pick<ManyToManyRelationField<any, any>, 'read' | 'write'>>): ManyToManyRelationField<any, any>;
38
+ export type RelationHelpers = {
39
+ manyToMany: typeof manyToMany;
40
+ pkList: typeof pkList;
41
+ nested: typeof nested;
42
+ slugList: typeof slugList;
43
+ };
44
+ export declare const relation: RelationHelpers;
45
+ export {};
@@ -5,12 +5,13 @@ import { APIView } from './APIView';
5
5
  import { RequestContext } from '../context/index';
6
6
  import type { FilterSet } from '../filters/index';
7
7
  import type { GenericAPIViewOpenAPIDescription } from '../resource/index';
8
- import type { ModelSerializerClass, SerializerOutput, SerializerSchema } from '../serializer/index';
9
- export interface GenericAPIViewConfig<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> {
8
+ import type { AnyModelSerializer, SerializerOutput } from '../serializer/index';
9
+ type SearchFieldRef<TModel extends Record<string, unknown>> = Extract<keyof TModel, string> | string;
10
+ export interface GenericAPIViewConfig<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> {
10
11
  serializer: TSerializer;
11
12
  filters?: FilterSet<TModel>;
12
13
  orderingFields?: (keyof TModel)[];
13
- searchFields?: (keyof TModel)[];
14
+ searchFields?: SearchFieldRef<TModel>[];
14
15
  lookupField?: keyof TModel;
15
16
  lookupParam?: string;
16
17
  paginatorFactory?: (queryset: QuerySet<TModel>) => Paginator<TModel, SerializerOutput<TSerializer>>;
@@ -18,11 +19,11 @@ export interface GenericAPIViewConfig<TModel extends Record<string, unknown>, TS
18
19
  /**
19
20
  * Generic API base class that centralizes query/build/validation helpers.
20
21
  */
21
- export declare abstract class GenericAPIView<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends APIView {
22
+ export declare abstract class GenericAPIView<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends APIView {
22
23
  protected readonly serializerClass: TSerializer;
23
24
  protected readonly filters?: FilterSet<TModel>;
24
25
  protected readonly orderingFields: readonly (keyof TModel)[];
25
- protected readonly searchFields: readonly (keyof TModel)[];
26
+ protected readonly searchFields: readonly SearchFieldRef<TModel>[];
26
27
  protected readonly lookupField?: keyof TModel;
27
28
  protected readonly lookupParam: string;
28
29
  protected readonly paginatorFactory?: (queryset: QuerySet<TModel>) => Paginator<TModel, SerializerOutput<TSerializer>>;
@@ -56,3 +57,4 @@ export declare abstract class GenericAPIView<TModel extends Record<string, unkno
56
57
  private requireModelMetadata;
57
58
  private getLookupFieldFromMetadata;
58
59
  }
60
+ export {};
@@ -1,10 +1,10 @@
1
1
  import { CreateModelMixin } from '../mixins/CreateModelMixin';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Generic API view for endpoints that only support resource creation.
7
7
  */
8
- export declare abstract class CreateAPIView<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends CreateModelMixin<TModel, TSerializer> {
8
+ export declare abstract class CreateAPIView<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends CreateModelMixin<TModel, TSerializer> {
9
9
  protected post(ctx: RequestContext): Promise<TangoResponse>;
10
10
  }
@@ -1,10 +1,10 @@
1
1
  import { ListModelMixin } from '../mixins/ListModelMixin';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Generic API view for endpoints that only expose a list operation.
7
7
  */
8
- export declare abstract class ListAPIView<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends ListModelMixin<TModel, TSerializer> {
8
+ export declare abstract class ListAPIView<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends ListModelMixin<TModel, TSerializer> {
9
9
  protected get(ctx: RequestContext): Promise<TangoResponse>;
10
10
  }
@@ -1,11 +1,11 @@
1
1
  import { GenericAPIView } from '../GenericAPIView';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Generic API view for collection endpoints that list and create resources.
7
7
  */
8
- export declare abstract class ListCreateAPIView<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends GenericAPIView<TModel, TSerializer> {
8
+ export declare abstract class ListCreateAPIView<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends GenericAPIView<TModel, TSerializer> {
9
9
  protected get(ctx: RequestContext): Promise<TangoResponse>;
10
10
  protected post(ctx: RequestContext): Promise<TangoResponse>;
11
11
  }
@@ -1,10 +1,10 @@
1
1
  import { RetrieveModelMixin } from '../mixins/RetrieveModelMixin';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Generic API view for endpoints that retrieve a single resource by lookup.
7
7
  */
8
- export declare abstract class RetrieveAPIView<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends RetrieveModelMixin<TModel, TSerializer> {
8
+ export declare abstract class RetrieveAPIView<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends RetrieveModelMixin<TModel, TSerializer> {
9
9
  protected get(ctx: RequestContext): Promise<TangoResponse>;
10
10
  }
@@ -1,11 +1,11 @@
1
1
  import { GenericAPIView } from '../GenericAPIView';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Generic API view for endpoints that retrieve and delete a single resource.
7
7
  */
8
- export declare abstract class RetrieveDestroyAPIView<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends GenericAPIView<TModel, TSerializer> {
8
+ export declare abstract class RetrieveDestroyAPIView<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends GenericAPIView<TModel, TSerializer> {
9
9
  protected get(ctx: RequestContext): Promise<TangoResponse>;
10
10
  protected delete(ctx: RequestContext): Promise<TangoResponse>;
11
11
  }
@@ -1,11 +1,11 @@
1
1
  import { GenericAPIView } from '../GenericAPIView';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Generic API view for endpoints that retrieve and update a single resource.
7
7
  */
8
- export declare abstract class RetrieveUpdateAPIView<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends GenericAPIView<TModel, TSerializer> {
8
+ export declare abstract class RetrieveUpdateAPIView<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends GenericAPIView<TModel, TSerializer> {
9
9
  protected get(ctx: RequestContext): Promise<TangoResponse>;
10
10
  protected put(ctx: RequestContext): Promise<TangoResponse>;
11
11
  protected patch(ctx: RequestContext): Promise<TangoResponse>;
@@ -1,11 +1,11 @@
1
1
  import { GenericAPIView } from '../GenericAPIView';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Generic API view for full detail endpoints that retrieve, update, and delete.
7
7
  */
8
- export declare abstract class RetrieveUpdateDestroyAPIView<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends GenericAPIView<TModel, TSerializer> {
8
+ export declare abstract class RetrieveUpdateDestroyAPIView<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends GenericAPIView<TModel, TSerializer> {
9
9
  protected get(ctx: RequestContext): Promise<TangoResponse>;
10
10
  protected put(ctx: RequestContext): Promise<TangoResponse>;
11
11
  protected patch(ctx: RequestContext): Promise<TangoResponse>;
@@ -1,3 +1,3 @@
1
- import { APIView, CreateAPIView, CreateModelMixin, DestroyModelMixin, GenericAPIView, ListAPIView, ListCreateAPIView, ListModelMixin, RetrieveAPIView, RetrieveDestroyAPIView, RetrieveModelMixin, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, UpdateModelMixin } from "../view-Djm3cQ6C.js";
1
+ import { APIView, CreateAPIView, CreateModelMixin, DestroyModelMixin, GenericAPIView, ListAPIView, ListCreateAPIView, ListModelMixin, RetrieveAPIView, RetrieveDestroyAPIView, RetrieveModelMixin, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, UpdateModelMixin } from "../view-iXGdHuS-.js";
2
2
 
3
3
  export { APIView, CreateAPIView, CreateModelMixin, DestroyModelMixin, GenericAPIView, ListAPIView, ListCreateAPIView, ListModelMixin, RetrieveAPIView, RetrieveDestroyAPIView, RetrieveModelMixin, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, UpdateModelMixin };
@@ -1,11 +1,11 @@
1
1
  import { GenericAPIView } from '../GenericAPIView';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Mixin that wires `POST` requests to the generic create implementation.
7
7
  */
8
- export declare abstract class CreateModelMixin<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends GenericAPIView<TModel, TSerializer> {
8
+ export declare abstract class CreateModelMixin<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends GenericAPIView<TModel, TSerializer> {
9
9
  protected create(ctx: RequestContext): Promise<TangoResponse>;
10
10
  protected post(ctx: RequestContext): Promise<TangoResponse>;
11
11
  }
@@ -1,11 +1,11 @@
1
1
  import { GenericAPIView } from '../GenericAPIView';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Mixin that wires `DELETE` requests to the generic destroy implementation.
7
7
  */
8
- export declare abstract class DestroyModelMixin<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends GenericAPIView<TModel, TSerializer> {
8
+ export declare abstract class DestroyModelMixin<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends GenericAPIView<TModel, TSerializer> {
9
9
  protected destroy(ctx: RequestContext): Promise<TangoResponse>;
10
10
  protected delete(ctx: RequestContext): Promise<TangoResponse>;
11
11
  }
@@ -1,11 +1,11 @@
1
1
  import { GenericAPIView } from '../GenericAPIView';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Mixin that wires `GET` requests to the generic list implementation.
7
7
  */
8
- export declare abstract class ListModelMixin<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends GenericAPIView<TModel, TSerializer> {
8
+ export declare abstract class ListModelMixin<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends GenericAPIView<TModel, TSerializer> {
9
9
  protected list(ctx: RequestContext): Promise<TangoResponse>;
10
10
  protected get(ctx: RequestContext): Promise<TangoResponse>;
11
11
  }
@@ -1,11 +1,11 @@
1
1
  import { GenericAPIView } from '../GenericAPIView';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Mixin that wires `GET` requests to the generic retrieve implementation.
7
7
  */
8
- export declare abstract class RetrieveModelMixin<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends GenericAPIView<TModel, TSerializer> {
8
+ export declare abstract class RetrieveModelMixin<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends GenericAPIView<TModel, TSerializer> {
9
9
  protected retrieve(ctx: RequestContext): Promise<TangoResponse>;
10
10
  protected get(ctx: RequestContext): Promise<TangoResponse>;
11
11
  }
@@ -1,11 +1,11 @@
1
1
  import { GenericAPIView } from '../GenericAPIView';
2
2
  import type { TangoResponse } from '@danceroutine/tango-core';
3
3
  import { RequestContext } from '../../context/index';
4
- import type { ModelSerializerClass, SerializerSchema } from '../../serializer/index';
4
+ import type { AnyModelSerializer } from '../../serializer/index';
5
5
  /**
6
6
  * Mixin that wires `PUT` and `PATCH` requests to the generic update implementation.
7
7
  */
8
- export declare abstract class UpdateModelMixin<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> extends GenericAPIView<TModel, TSerializer> {
8
+ export declare abstract class UpdateModelMixin<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> extends GenericAPIView<TModel, TSerializer> {
9
9
  protected update(ctx: RequestContext): Promise<TangoResponse>;
10
10
  protected put(ctx: RequestContext): Promise<TangoResponse>;
11
11
  protected patch(ctx: RequestContext): Promise<TangoResponse>;
@@ -412,7 +412,8 @@ var GenericAPIView = class extends APIView {
412
412
  const totalCountPromise = paginator.needsTotalCount() ? qs.count() : Promise.resolve(undefined);
413
413
  const [result, totalCount] = await Promise.all([resultPromise, totalCountPromise]);
414
414
  const serializer = this.getSerializer();
415
- const response = paginator.toResponse(result.map((row) => serializer.toRepresentation(row)), { totalCount });
415
+ const rows = await serializer.serializeMany(result.items);
416
+ const response = paginator.toResponse(rows, { totalCount });
416
417
  return TangoResponse.json(response, { status: 200 });
417
418
  } catch (error) {
418
419
  return this.handleError(error);
@@ -435,7 +436,7 @@ var GenericAPIView = class extends APIView {
435
436
  const filterByLookup = { [lookupField]: value };
436
437
  const result = await this.getManager().query().filter(filterByLookup).fetchOne();
437
438
  if (!result) throw new NotFoundError(`No ${this.getManager().meta.table} record found for ${String(lookupField)}=${value}.`);
438
- return TangoResponse.json(this.getSerializer().toRepresentation(result), { status: 200 });
439
+ return TangoResponse.json(await this.getSerializer().serialize(result), { status: 200 });
439
440
  } catch (error) {
440
441
  return this.handleError(error);
441
442
  }
@@ -634,4 +635,4 @@ __export(view_exports, {
634
635
 
635
636
  //#endregion
636
637
  export { APIView, BasePaginator, CreateAPIView, CreateModelMixin, DestroyModelMixin, GenericAPIView, ListAPIView, ListCreateAPIView, ListModelMixin, OffsetPaginationInput, OffsetPaginator, RetrieveAPIView, RetrieveDestroyAPIView, RetrieveModelMixin, RetrieveUpdateAPIView, RetrieveUpdateDestroyAPIView, UpdateModelMixin, __export, inferModelFieldParsers, view_exports };
637
- //# sourceMappingURL=view-Djm3cQ6C.js.map
638
+ //# sourceMappingURL=view-iXGdHuS-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-iXGdHuS-.js","names":["rows: readonly T[] | QueryResult<T>","OffsetPaginationInput: z.ZodType<OffsetPaginationInputValue>","results: T[]","pageNumber: number","perPage: number","totalCount?: number","value: unknown","queryset: QuerySet<T>","params: TangoQueryParams","results: readonly TResult[] | QueryResult<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/BasePaginator.ts","../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 { QueryResult } from '@danceroutine/tango-orm';\n\nexport abstract class BasePaginator {\n protected resolveQueryResultRows<T>(rows: readonly T[] | QueryResult<T>): T[] {\n if (QueryResult.isQueryResult<T>(rows)) {\n return rows.toArray();\n }\n return [...rows];\n }\n}\n","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 { QueryResult, QuerySet } from '@danceroutine/tango-orm';\nimport { BasePaginator } from '../pagination/BasePaginator';\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>>\n extends BasePaginator\n implements Paginator<T, T, OffsetPaginatedResponse<T>>\n{\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 super();\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: readonly TResult[] | QueryResult<TResult>,\n context?: { totalCount?: number; params?: TangoQueryParams }\n ): OffsetPaginatedResponse<TResult> {\n const totalCount = context?.totalCount;\n const response: OffsetPaginatedResponse<TResult> = { results: this.resolveQueryResultRows(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: readonly TResult[] | QueryResult<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(this.resolveQueryResultRows(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 { AnyModelSerializer, SerializerOutput } from '../serializer/index';\nimport type { ResourceModelLike } from '../resource/index';\n\ntype SearchFieldRef<TModel extends Record<string, unknown>> = Extract<keyof TModel, string> | string;\n\nexport interface GenericAPIViewConfig<\n TModel extends Record<string, unknown>,\n TSerializer extends AnyModelSerializer<TModel>,\n> {\n serializer: TSerializer;\n filters?: FilterSet<TModel>;\n orderingFields?: (keyof TModel)[];\n searchFields?: SearchFieldRef<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 AnyModelSerializer<TModel>,\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 SearchFieldRef<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 rows = await serializer.serializeMany(result.items);\n const response = paginator.toResponse(rows 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((await this.getSerializer().serialize(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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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 { AnyModelSerializer } 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 AnyModelSerializer<TModel>,\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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"}
@@ -4,7 +4,7 @@ import type { RequestContext } from '../context/index';
4
4
  import type { FilterSet } from '../filters/index';
5
5
  import type { Paginator } from '../pagination/index';
6
6
  import type { ModelViewSetOpenAPIDescription } from '../resource/index';
7
- import type { AnyModelSerializerClass, ModelSerializerClass, SerializerOutput, SerializerSchema } from '../serializer/index';
7
+ import type { AnyModelSerializer, AnyModelSerializerClass, SerializerOutput } from '../serializer/index';
8
8
  export type ViewSetActionScope = 'detail' | 'collection';
9
9
  export type ViewSetActionMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
10
10
  export interface ViewSetActionDescriptor {
@@ -17,10 +17,11 @@ export interface ResolvedViewSetActionDescriptor extends ViewSetActionDescriptor
17
17
  path: string;
18
18
  }
19
19
  type AnyModelViewSet = ModelViewSet<Record<string, unknown>, AnyModelSerializerClass>;
20
+ type SearchFieldRef<TModel extends Record<string, unknown>> = Extract<keyof TModel, string> | string;
20
21
  /**
21
22
  * Configuration for a ModelViewSet, defining how a serializer-backed model is exposed as an API resource.
22
23
  */
23
- export interface ModelViewSetConfig<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> {
24
+ export interface ModelViewSetConfig<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> {
24
25
  /** Serializer class that owns validation, representation, and persistence hooks */
25
26
  serializer: TSerializer;
26
27
  /** Optional filter set defining which query parameters can filter the list endpoint */
@@ -28,7 +29,7 @@ export interface ModelViewSetConfig<TModel extends Record<string, unknown>, TSer
28
29
  /** Fields that clients are allowed to sort by via query parameters */
29
30
  orderingFields?: (keyof TModel)[];
30
31
  /** Fields that are searched when a free-text search query parameter is provided */
31
- searchFields?: (keyof TModel)[];
32
+ searchFields?: SearchFieldRef<TModel>[];
32
33
  /** Optional paginator factory used by list endpoints. */
33
34
  paginatorFactory?: (queryset: QuerySet<TModel>) => Paginator<TModel, SerializerOutput<TSerializer>>;
34
35
  }
@@ -37,14 +38,14 @@ export interface ModelViewSetConfig<TModel extends Record<string, unknown>, TSer
37
38
  * Provides list, retrieve, create, update, and delete methods with filtering,
38
39
  * search, pagination, and ordering support.
39
40
  */
40
- export declare abstract class ModelViewSet<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> {
41
+ export declare abstract class ModelViewSet<TModel extends Record<string, unknown>, TSerializer extends AnyModelSerializer<TModel>> {
41
42
  static readonly BRAND: "tango.resources.model_view_set";
42
43
  static readonly actions: readonly ViewSetActionDescriptor[];
43
44
  readonly __tangoBrand: typeof ModelViewSet.BRAND;
44
45
  protected readonly serializerClass: TSerializer;
45
46
  protected readonly filters?: FilterSet<TModel>;
46
47
  protected readonly orderingFields: (keyof TModel)[];
47
- protected readonly searchFields: (keyof TModel)[];
48
+ protected readonly searchFields: SearchFieldRef<TModel>[];
48
49
  protected readonly paginatorFactory?: (queryset: QuerySet<TModel>) => Paginator<TModel, SerializerOutput<TSerializer>>;
49
50
  private serializer?;
50
51
  constructor(config: ModelViewSetConfig<TModel, TSerializer>);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danceroutine/tango-resources",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
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-core": "1.7.0",
62
- "@danceroutine/tango-orm": "1.7.0"
61
+ "@danceroutine/tango-core": "1.8.1",
62
+ "@danceroutine/tango-orm": "1.8.1"
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.7.0",
71
- "@danceroutine/tango-testing": "1.7.0"
70
+ "@danceroutine/tango-schema": "1.8.1",
71
+ "@danceroutine/tango-testing": "1.8.1"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "tsdown",