@danceroutine/tango-resources 0.1.0 → 1.0.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/LICENSE +21 -0
- package/README.md +170 -0
- package/dist/context/RequestContext.d.ts +23 -4
- package/dist/filters/FilterSet.d.ts +59 -4
- package/dist/filters/index.d.ts +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.js +515 -205
- package/dist/index.js.map +1 -1
- package/dist/pagination/CursorPaginationInput.d.ts +7 -0
- package/dist/pagination/OffsetPaginationInput.d.ts +7 -0
- package/dist/pagination/PaginatedResponse.d.ts +8 -2
- package/dist/pagination/Paginator.d.ts +5 -3
- package/dist/pagination/index.d.ts +5 -3
- package/dist/paginators/CursorPaginator.d.ts +32 -6
- package/dist/paginators/OffsetPaginator.d.ts +30 -7
- package/dist/resource/OpenAPIDescription.d.ts +21 -0
- package/dist/resource/ResourceModelLike.d.ts +16 -0
- package/dist/resource/index.d.ts +5 -0
- package/dist/serializer/ModelSerializer.d.ts +47 -0
- package/dist/serializer/Serializer.d.ts +52 -0
- package/dist/serializer/index.d.ts +5 -0
- package/dist/view/APIView.d.ts +26 -0
- package/dist/view/GenericAPIView.d.ts +57 -0
- package/dist/view/generics/CreateAPIView.d.ts +10 -0
- package/dist/view/generics/ListAPIView.d.ts +10 -0
- package/dist/view/generics/ListCreateAPIView.d.ts +11 -0
- package/dist/view/generics/RetrieveAPIView.d.ts +10 -0
- package/dist/view/generics/RetrieveDestroyAPIView.d.ts +11 -0
- package/dist/view/generics/RetrieveUpdateAPIView.d.ts +12 -0
- package/dist/view/generics/RetrieveUpdateDestroyAPIView.d.ts +13 -0
- package/dist/view/generics/index.d.ts +10 -0
- package/dist/view/index.d.ts +8 -0
- package/dist/view/index.js +3 -0
- package/dist/view/mixins/CreateModelMixin.d.ts +11 -0
- package/dist/view/mixins/DestroyModelMixin.d.ts +11 -0
- package/dist/view/mixins/ListModelMixin.d.ts +11 -0
- package/dist/view/mixins/RetrieveModelMixin.d.ts +11 -0
- package/dist/view/mixins/UpdateModelMixin.d.ts +12 -0
- package/dist/view/mixins/index.d.ts +8 -0
- package/dist/view-BNGEURL_.js +547 -0
- package/dist/view-BNGEURL_.js.map +1 -0
- package/dist/viewset/ModelViewSet.d.ts +91 -45
- package/dist/viewset/index.d.ts +2 -1
- package/package.json +75 -69
- package/dist/domain/index.d.ts +0 -8
- package/dist/pagination/PaginationInput.d.ts +0 -7
- package/dist/viewset/ModelViewSet.js +0 -143
|
@@ -1,64 +1,110 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
1
|
+
import type { ManagerLike, QuerySet } from '@danceroutine/tango-orm';
|
|
2
|
+
import { TangoResponse } from '@danceroutine/tango-core';
|
|
3
3
|
import type { RequestContext } from '../context/index';
|
|
4
4
|
import type { FilterSet } from '../filters/index';
|
|
5
|
+
import type { Paginator } from '../pagination/index';
|
|
6
|
+
import type { ModelViewSetOpenAPIDescription } from '../resource/index';
|
|
7
|
+
import type { AnyModelSerializerClass, ModelSerializerClass, SerializerOutput, SerializerSchema } from '../serializer/index';
|
|
8
|
+
export type ViewSetActionScope = 'detail' | 'collection';
|
|
9
|
+
export type ViewSetActionMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
10
|
+
export interface ViewSetActionDescriptor {
|
|
11
|
+
name: string;
|
|
12
|
+
scope: ViewSetActionScope;
|
|
13
|
+
methods: readonly ViewSetActionMethod[];
|
|
14
|
+
path?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ResolvedViewSetActionDescriptor extends ViewSetActionDescriptor {
|
|
17
|
+
path: string;
|
|
18
|
+
}
|
|
19
|
+
type AnyModelViewSet = ModelViewSet<Record<string, unknown>, AnyModelSerializerClass>;
|
|
5
20
|
/**
|
|
6
|
-
* Configuration for a ModelViewSet, defining how a model is exposed as an API resource.
|
|
7
|
-
*
|
|
8
|
-
* @template TModel - The record type of the underlying database model
|
|
9
|
-
* @template TRead - Zod schema used to validate and shape read (GET) responses
|
|
10
|
-
* @template TWrite - Zod schema used to validate incoming write (POST/PUT) request bodies
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* const config: ModelViewSetConfig<Post, typeof PostReadSchema, typeof PostWriteSchema> = {
|
|
15
|
-
* repository: postRepository,
|
|
16
|
-
* readSchema: PostReadSchema,
|
|
17
|
-
* writeSchema: PostWriteSchema,
|
|
18
|
-
* updateSchema: PostPatchSchema,
|
|
19
|
-
* filters: postFilterSet,
|
|
20
|
-
* orderingFields: ['createdAt', 'title'],
|
|
21
|
-
* searchFields: ['title', 'body'],
|
|
22
|
-
* };
|
|
23
|
-
* ```
|
|
21
|
+
* Configuration for a ModelViewSet, defining how a serializer-backed model is exposed as an API resource.
|
|
24
22
|
*/
|
|
25
|
-
export interface ModelViewSetConfig<TModel extends Record<string, unknown>,
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
/** Zod schema applied to outgoing data (list and detail responses) */
|
|
29
|
-
readSchema: TRead;
|
|
30
|
-
/** Zod schema applied to incoming data for create operations */
|
|
31
|
-
writeSchema: TWrite;
|
|
32
|
-
/** Optional Zod schema for partial updates (PATCH). Falls back to writeSchema if omitted */
|
|
33
|
-
updateSchema?: z.ZodType<Partial<TModel>>;
|
|
23
|
+
export interface ModelViewSetConfig<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> {
|
|
24
|
+
/** Serializer class that owns validation, representation, and persistence hooks */
|
|
25
|
+
serializer: TSerializer;
|
|
34
26
|
/** Optional filter set defining which query parameters can filter the list endpoint */
|
|
35
27
|
filters?: FilterSet<TModel>;
|
|
36
28
|
/** Fields that clients are allowed to sort by via query parameters */
|
|
37
29
|
orderingFields?: (keyof TModel)[];
|
|
38
30
|
/** Fields that are searched when a free-text search query parameter is provided */
|
|
39
31
|
searchFields?: (keyof TModel)[];
|
|
32
|
+
/** Optional paginator factory used by list endpoints. */
|
|
33
|
+
paginatorFactory?: (queryset: QuerySet<TModel>) => Paginator<TModel, SerializerOutput<TSerializer>>;
|
|
40
34
|
}
|
|
41
35
|
/**
|
|
42
36
|
* Base class for creating RESTful API viewsets with built-in CRUD operations.
|
|
43
37
|
* Provides list, retrieve, create, update, and delete methods with filtering,
|
|
44
38
|
* search, pagination, and ordering support.
|
|
45
39
|
*/
|
|
46
|
-
export declare abstract class ModelViewSet<TModel extends Record<string, unknown>,
|
|
40
|
+
export declare abstract class ModelViewSet<TModel extends Record<string, unknown>, TSerializer extends ModelSerializerClass<TModel, SerializerSchema, SerializerSchema, SerializerSchema>> {
|
|
47
41
|
static readonly BRAND: "tango.resources.model_view_set";
|
|
48
|
-
|
|
49
|
-
protected readSchema: TRead;
|
|
50
|
-
protected writeSchema: TWrite;
|
|
51
|
-
protected updateSchema: z.ZodType<Partial<TModel>>;
|
|
52
|
-
protected filters?: FilterSet<TModel>;
|
|
53
|
-
protected orderingFields: (keyof TModel)[];
|
|
54
|
-
protected searchFields: (keyof TModel)[];
|
|
42
|
+
static readonly actions: readonly ViewSetActionDescriptor[];
|
|
55
43
|
readonly __tangoBrand: typeof ModelViewSet.BRAND;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
44
|
+
protected readonly serializerClass: TSerializer;
|
|
45
|
+
protected readonly filters?: FilterSet<TModel>;
|
|
46
|
+
protected readonly orderingFields: (keyof TModel)[];
|
|
47
|
+
protected readonly searchFields: (keyof TModel)[];
|
|
48
|
+
protected readonly paginatorFactory?: (queryset: QuerySet<TModel>) => Paginator<TModel, SerializerOutput<TSerializer>>;
|
|
49
|
+
private serializer?;
|
|
50
|
+
constructor(config: ModelViewSetConfig<TModel, TSerializer>);
|
|
51
|
+
/**
|
|
52
|
+
* Return the custom action descriptors declared by a viewset or constructor.
|
|
53
|
+
*/
|
|
54
|
+
static getActions(viewsetOrConstructor: AnyModelViewSet | (new (...args: never[]) => AnyModelViewSet)): readonly ResolvedViewSetActionDescriptor[];
|
|
55
|
+
/**
|
|
56
|
+
* Narrow an unknown value to `ModelViewSet`.
|
|
57
|
+
*/
|
|
58
|
+
static isModelViewSet(value: unknown): value is ModelViewSet<Record<string, unknown>, AnyModelSerializerClass>;
|
|
59
|
+
/**
|
|
60
|
+
* Preserve literal action inference while validating the descriptor shape.
|
|
61
|
+
*/
|
|
62
|
+
static defineViewSetActions<const T extends readonly ViewSetActionDescriptor[]>(actions: T): T;
|
|
63
|
+
private static resolvePathFromDescriptor;
|
|
64
|
+
private static toKebabCase;
|
|
65
|
+
/**
|
|
66
|
+
* Return the serializer class that owns this resource contract.
|
|
67
|
+
*/
|
|
68
|
+
getSerializerClass(): TSerializer;
|
|
69
|
+
/**
|
|
70
|
+
* Return the serializer instance for the current resource.
|
|
71
|
+
*/
|
|
72
|
+
getSerializer(): InstanceType<TSerializer>;
|
|
73
|
+
/**
|
|
74
|
+
* Describe the public HTTP contract that this resource contributes to OpenAPI generation.
|
|
75
|
+
*/
|
|
76
|
+
describeOpenAPI(): ModelViewSetOpenAPIDescription<TModel, TSerializer>;
|
|
77
|
+
/**
|
|
78
|
+
* List endpoint with filtering, search, ordering, and offset pagination.
|
|
79
|
+
*/
|
|
80
|
+
list(ctx: RequestContext): Promise<TangoResponse>;
|
|
81
|
+
/**
|
|
82
|
+
* Retrieve endpoint for a single resource by id.
|
|
83
|
+
*/
|
|
84
|
+
retrieve(_ctx: RequestContext, id: string): Promise<TangoResponse>;
|
|
85
|
+
/**
|
|
86
|
+
* Create endpoint: validate input, persist, and return serialized output.
|
|
87
|
+
*/
|
|
88
|
+
create(ctx: RequestContext): Promise<TangoResponse>;
|
|
89
|
+
/**
|
|
90
|
+
* Update endpoint: validate partial payload and persist by id.
|
|
91
|
+
*/
|
|
92
|
+
update(ctx: RequestContext, id: string): Promise<TangoResponse>;
|
|
93
|
+
/**
|
|
94
|
+
* Destroy endpoint: delete a resource by id.
|
|
95
|
+
*/
|
|
96
|
+
destroy(_ctx: RequestContext, id: string): Promise<TangoResponse>;
|
|
97
|
+
protected getPaginator(queryset: QuerySet<TModel>): Paginator<TModel, SerializerOutput<TSerializer>>;
|
|
98
|
+
protected getManager(): ManagerLike<TModel>;
|
|
99
|
+
/**
|
|
100
|
+
* Convert thrown errors into normalized HTTP responses.
|
|
101
|
+
*/
|
|
102
|
+
protected handleError(error: unknown): TangoResponse;
|
|
103
|
+
/**
|
|
104
|
+
* Resolve route path segment(s) for a custom action.
|
|
105
|
+
* Override this in subclasses to customize path derivation globally.
|
|
106
|
+
*/
|
|
107
|
+
protected resolveActionPath(action: ViewSetActionDescriptor): string;
|
|
108
|
+
private requireModelMetadata;
|
|
64
109
|
}
|
|
110
|
+
export {};
|
package/dist/viewset/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Domain boundary barrel: centralizes this subdomain's public contract.
|
|
3
3
|
*/
|
|
4
|
-
export { ModelViewSet, type ModelViewSetConfig } from './ModelViewSet';
|
|
4
|
+
export { ModelViewSet, type ModelViewSetConfig, type ViewSetActionDescriptor, type ViewSetActionMethod, type ViewSetActionScope, type ResolvedViewSetActionDescriptor, } from './ModelViewSet';
|
|
5
|
+
export type { ModelViewSetOpenAPIDescription } from '../resource/index';
|
package/package.json
CHANGED
|
@@ -1,75 +1,81 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
"./context": {
|
|
14
|
-
"types": "./dist/index.d.ts",
|
|
15
|
-
"import": "./dist/index.js"
|
|
16
|
-
},
|
|
17
|
-
"./filters": {
|
|
18
|
-
"types": "./dist/index.d.ts",
|
|
19
|
-
"import": "./dist/index.js"
|
|
20
|
-
},
|
|
21
|
-
"./pagination": {
|
|
22
|
-
"types": "./dist/index.d.ts",
|
|
23
|
-
"import": "./dist/index.js"
|
|
24
|
-
},
|
|
25
|
-
"./paginators": {
|
|
26
|
-
"types": "./dist/index.d.ts",
|
|
27
|
-
"import": "./dist/index.js"
|
|
28
|
-
},
|
|
29
|
-
"./viewset": {
|
|
30
|
-
"types": "./dist/index.d.ts",
|
|
31
|
-
"import": "./dist/index.js"
|
|
32
|
-
},
|
|
33
|
-
"./domain": {
|
|
34
|
-
"types": "./dist/index.d.ts",
|
|
35
|
-
"import": "./dist/index.js"
|
|
36
|
-
}
|
|
2
|
+
"name": "@danceroutine/tango-resources",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "ModelViewSet, serializers, filters, and pagination for Tango",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
37
12
|
},
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"scripts": {
|
|
42
|
-
"build": "tsdown",
|
|
43
|
-
"test": "vitest run --coverage",
|
|
44
|
-
"test:watch": "vitest",
|
|
45
|
-
"typecheck": "tsc --noEmit"
|
|
13
|
+
"./context": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
46
16
|
},
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"viewset",
|
|
51
|
-
"crud",
|
|
52
|
-
"rest"
|
|
53
|
-
],
|
|
54
|
-
"author": "Pedro Del Moral Lopez",
|
|
55
|
-
"license": "MIT",
|
|
56
|
-
"repository": {
|
|
57
|
-
"type": "git",
|
|
58
|
-
"url": "https://github.com/danceroutine/tango.git",
|
|
59
|
-
"directory": "packages/resources"
|
|
17
|
+
"./filters": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
60
20
|
},
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"zod": "^4.0.0"
|
|
21
|
+
"./pagination": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
65
24
|
},
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
25
|
+
"./paginators": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./serializer": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./viewset": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./view": {
|
|
38
|
+
"types": "./dist/view/index.d.ts",
|
|
39
|
+
"import": "./dist/view/index.js"
|
|
74
40
|
}
|
|
75
|
-
}
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist"
|
|
44
|
+
],
|
|
45
|
+
"keywords": [
|
|
46
|
+
"tango",
|
|
47
|
+
"resources",
|
|
48
|
+
"viewset",
|
|
49
|
+
"crud",
|
|
50
|
+
"rest"
|
|
51
|
+
],
|
|
52
|
+
"author": "Pedro Del Moral Lopez",
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/danceroutine/tango.git",
|
|
57
|
+
"directory": "packages/resources"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"zod": "^4.0.0",
|
|
61
|
+
"@danceroutine/tango-core": "1.0.0",
|
|
62
|
+
"@danceroutine/tango-orm": "1.0.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/node": "^22.9.0",
|
|
66
|
+
"tsdown": "^0.4.0",
|
|
67
|
+
"typescript": "^5.6.3",
|
|
68
|
+
"vitest": "^4.0.6",
|
|
69
|
+
"zod": "^4.0.0",
|
|
70
|
+
"@danceroutine/tango-testing": "1.0.0",
|
|
71
|
+
"@danceroutine/tango-schema": "1.0.0"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"build": "tsdown",
|
|
75
|
+
"test": "vitest run --coverage",
|
|
76
|
+
"test:watch": "vitest",
|
|
77
|
+
"typecheck": "pnpm run typecheck:prod && pnpm run typecheck:test",
|
|
78
|
+
"typecheck:prod": "tsc --noEmit -p tsconfig.json",
|
|
79
|
+
"typecheck:test": "tsc --noEmit -p tsconfig.tests.json"
|
|
80
|
+
}
|
|
81
|
+
}
|
package/dist/domain/index.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compatibility aggregate barrel. Canonical source-of-truth types now live in
|
|
3
|
-
* `filters`, `pagination`, and `viewset` subdomains.
|
|
4
|
-
*/
|
|
5
|
-
export type { FilterType, RangeOperator } from '../filters/index';
|
|
6
|
-
export type { ModelViewSetConfig } from '../viewset/index';
|
|
7
|
-
export type { PaginatedResponse, Paginator, Page } from '../pagination/index';
|
|
8
|
-
export { PaginationInput } from '../pagination/index';
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { OffsetPaginator } from '../paginators/OffsetPaginator';
|
|
2
|
-
import { toHttpError } from '@danceroutine/tango-core';
|
|
3
|
-
import { Q } from '@danceroutine/tango-orm';
|
|
4
|
-
/**
|
|
5
|
-
* Base class for creating RESTful API viewsets with built-in CRUD operations.
|
|
6
|
-
* Provides list, retrieve, create, update, and delete methods with filtering,
|
|
7
|
-
* search, pagination, and ordering support.
|
|
8
|
-
*/
|
|
9
|
-
export class ModelViewSet {
|
|
10
|
-
static BRAND = 'tango.resources.model_view_set';
|
|
11
|
-
repository;
|
|
12
|
-
readSchema;
|
|
13
|
-
writeSchema;
|
|
14
|
-
updateSchema;
|
|
15
|
-
filters;
|
|
16
|
-
orderingFields;
|
|
17
|
-
searchFields;
|
|
18
|
-
__tangoBrand = ModelViewSet.BRAND;
|
|
19
|
-
static isModelViewSet(value) {
|
|
20
|
-
return (typeof value === 'object' &&
|
|
21
|
-
value !== null &&
|
|
22
|
-
value.__tangoBrand === ModelViewSet.BRAND);
|
|
23
|
-
}
|
|
24
|
-
constructor(config) {
|
|
25
|
-
this.repository = config.repository;
|
|
26
|
-
this.readSchema = config.readSchema;
|
|
27
|
-
this.writeSchema = config.writeSchema;
|
|
28
|
-
this.updateSchema = config.updateSchema ?? config.writeSchema.partial();
|
|
29
|
-
this.filters = config.filters;
|
|
30
|
-
this.orderingFields = config.orderingFields ?? [];
|
|
31
|
-
this.searchFields = config.searchFields ?? [];
|
|
32
|
-
}
|
|
33
|
-
async list(ctx) {
|
|
34
|
-
try {
|
|
35
|
-
const params = new URL(ctx.request.url).searchParams;
|
|
36
|
-
const paginator = new OffsetPaginator(this.repository.query());
|
|
37
|
-
paginator.parse(params);
|
|
38
|
-
let qs = this.repository.query();
|
|
39
|
-
if (this.filters) {
|
|
40
|
-
const filterInputs = this.filters.apply(params);
|
|
41
|
-
if (filterInputs.length > 0) {
|
|
42
|
-
qs = qs.filter(Q.and(...filterInputs));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const search = params.get('search');
|
|
46
|
-
if (search && this.searchFields.length > 0) {
|
|
47
|
-
const searchFilters = this.searchFields.map((field) => {
|
|
48
|
-
const lookup = `${String(field)}__icontains`;
|
|
49
|
-
return { [lookup]: search };
|
|
50
|
-
});
|
|
51
|
-
qs = qs.filter(Q.or(...searchFilters));
|
|
52
|
-
}
|
|
53
|
-
const ordering = params.get('ordering');
|
|
54
|
-
if (ordering) {
|
|
55
|
-
const orderTokens = ordering.split(',').filter((field) => {
|
|
56
|
-
const cleanField = field.startsWith('-') ? field.slice(1) : field;
|
|
57
|
-
return this.orderingFields.includes(cleanField);
|
|
58
|
-
});
|
|
59
|
-
if (orderTokens.length > 0) {
|
|
60
|
-
qs = qs.orderBy(...orderTokens.map((token) => token));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
qs = paginator.apply(qs);
|
|
64
|
-
const [result, totalCount] = await Promise.all([qs.fetch(this.readSchema), qs.count()]);
|
|
65
|
-
const response = paginator.toResponse(result.results, { totalCount });
|
|
66
|
-
return new Response(JSON.stringify(response), {
|
|
67
|
-
status: 200,
|
|
68
|
-
headers: { 'Content-Type': 'application/json' },
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
return this.handleError(error);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
async retrieve(_ctx, id) {
|
|
76
|
-
try {
|
|
77
|
-
const pk = this.repository.meta.pk;
|
|
78
|
-
const filterById = { [pk]: id };
|
|
79
|
-
const result = await this.repository.query().filter(filterById).fetchOne(this.readSchema);
|
|
80
|
-
if (!result) {
|
|
81
|
-
return new Response(JSON.stringify({ error: 'Not found' }), {
|
|
82
|
-
status: 404,
|
|
83
|
-
headers: { 'Content-Type': 'application/json' },
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
return new Response(JSON.stringify(result), {
|
|
87
|
-
status: 200,
|
|
88
|
-
headers: { 'Content-Type': 'application/json' },
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
return this.handleError(error);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
async create(ctx) {
|
|
96
|
-
try {
|
|
97
|
-
const body = await ctx.request.json();
|
|
98
|
-
const validated = this.writeSchema.parse(body);
|
|
99
|
-
const created = await this.repository.create(validated);
|
|
100
|
-
const result = this.readSchema.parse(created);
|
|
101
|
-
return new Response(JSON.stringify(result), {
|
|
102
|
-
status: 201,
|
|
103
|
-
headers: { 'Content-Type': 'application/json' },
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
return this.handleError(error);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
async update(ctx, id) {
|
|
111
|
-
try {
|
|
112
|
-
const body = await ctx.request.json();
|
|
113
|
-
const validated = this.updateSchema.parse(body);
|
|
114
|
-
const pkValue = id;
|
|
115
|
-
const updated = await this.repository.update(pkValue, validated);
|
|
116
|
-
const result = this.readSchema.parse(updated);
|
|
117
|
-
return new Response(JSON.stringify(result), {
|
|
118
|
-
status: 200,
|
|
119
|
-
headers: { 'Content-Type': 'application/json' },
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
catch (error) {
|
|
123
|
-
return this.handleError(error);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
async destroy(_ctx, id) {
|
|
127
|
-
try {
|
|
128
|
-
const pkValue = id;
|
|
129
|
-
await this.repository.delete(pkValue);
|
|
130
|
-
return new Response(null, { status: 204 });
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
return this.handleError(error);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
handleError(error) {
|
|
137
|
-
const httpError = toHttpError(error);
|
|
138
|
-
return new Response(JSON.stringify(httpError.body), {
|
|
139
|
-
status: httpError.status,
|
|
140
|
-
headers: { 'Content-Type': 'application/json' },
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
}
|