@impactor/nest 3.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 +49 -0
- package/index.d.ts +40 -0
- package/nx.json +190 -0
- package/package.json +102 -0
- package/src/configs/app.d.ts +21 -0
- package/src/configs/app.js +65 -0
- package/src/configs/app.js.map +1 -0
- package/src/configs/database.d.ts +2 -0
- package/src/configs/database.js +23 -0
- package/src/configs/database.js.map +1 -0
- package/src/configs/microservice.d.ts +7 -0
- package/src/configs/microservice.js +26 -0
- package/src/configs/microservice.js.map +1 -0
- package/src/configs/multi-queue-rabbitmq-server.d.ts +24 -0
- package/src/configs/multi-queue-rabbitmq-server.js +91 -0
- package/src/configs/multi-queue-rabbitmq-server.js.map +1 -0
- package/src/configs/redis-config.d.ts +5 -0
- package/src/configs/redis-config.js +16 -0
- package/src/configs/redis-config.js.map +1 -0
- package/src/create-app.d.ts +3 -0
- package/src/create-app.js +104 -0
- package/src/create-app.js.map +1 -0
- package/src/create-microservice.d.ts +2 -0
- package/src/create-microservice.js +43 -0
- package/src/create-microservice.js.map +1 -0
- package/src/create-testing-microservice.d.ts +3 -0
- package/src/create-testing-microservice.js +30 -0
- package/src/create-testing-microservice.js.map +1 -0
- package/src/decorators/controller/add-decorators/add-controller-path.d.ts +2 -0
- package/src/decorators/controller/add-decorators/add-controller-path.js +22 -0
- package/src/decorators/controller/add-decorators/add-controller-path.js.map +1 -0
- package/src/decorators/controller/add-decorators/add-controller-tags.d.ts +2 -0
- package/src/decorators/controller/add-decorators/add-controller-tags.js +28 -0
- package/src/decorators/controller/add-decorators/add-controller-tags.js.map +1 -0
- package/src/decorators/controller/add-decorators/add-http-method.d.ts +3 -0
- package/src/decorators/controller/add-decorators/add-http-method.js +27 -0
- package/src/decorators/controller/add-decorators/add-http-method.js.map +1 -0
- package/src/decorators/controller/add-decorators/add-swagger-body.d.ts +3 -0
- package/src/decorators/controller/add-decorators/add-swagger-body.js +30 -0
- package/src/decorators/controller/add-decorators/add-swagger-body.js.map +1 -0
- package/src/decorators/controller/add-decorators/add-swagger-operation.d.ts +3 -0
- package/src/decorators/controller/add-decorators/add-swagger-operation.js +21 -0
- package/src/decorators/controller/add-decorators/add-swagger-operation.js.map +1 -0
- package/src/decorators/controller/add-decorators/add-swagger-params.d.ts +3 -0
- package/src/decorators/controller/add-decorators/add-swagger-params.js +50 -0
- package/src/decorators/controller/add-decorators/add-swagger-params.js.map +1 -0
- package/src/decorators/controller/add-decorators/add-swagger-response.d.ts +3 -0
- package/src/decorators/controller/add-decorators/add-swagger-response.js +33 -0
- package/src/decorators/controller/add-decorators/add-swagger-response.js.map +1 -0
- package/src/decorators/controller/controller.decorator.d.ts +2 -0
- package/src/decorators/controller/controller.decorator.js +18 -0
- package/src/decorators/controller/controller.decorator.js.map +1 -0
- package/src/decorators/controller/controller.factory.d.ts +11 -0
- package/src/decorators/controller/controller.factory.js +261 -0
- package/src/decorators/controller/controller.factory.js.map +1 -0
- package/src/decorators/controller/default-options.d.ts +2 -0
- package/src/decorators/controller/default-options.js +181 -0
- package/src/decorators/controller/default-options.js.map +1 -0
- package/src/decorators/controller/dto/empty.dto.d.ts +2 -0
- package/src/decorators/controller/dto/empty.dto.js +18 -0
- package/src/decorators/controller/dto/empty.dto.js.map +1 -0
- package/src/decorators/controller/dto/update-response.dto.d.ts +3 -0
- package/src/decorators/controller/dto/update-response.dto.js +16 -0
- package/src/decorators/controller/dto/update-response.dto.js.map +1 -0
- package/src/decorators/controller/interceptors/query.interceptor.d.ts +8 -0
- package/src/decorators/controller/interceptors/query.interceptor.js +59 -0
- package/src/decorators/controller/interceptors/query.interceptor.js.map +1 -0
- package/src/decorators/controller/route-handler.d.ts +3 -0
- package/src/decorators/controller/route-handler.js +44 -0
- package/src/decorators/controller/route-handler.js.map +1 -0
- package/src/decorators/controller/route.decorator.d.ts +2 -0
- package/src/decorators/controller/route.decorator.js +17 -0
- package/src/decorators/controller/route.decorator.js.map +1 -0
- package/src/decorators/controller/services/crud-typeorm.service.d.ts +46 -0
- package/src/decorators/controller/services/crud-typeorm.service.js +232 -0
- package/src/decorators/controller/services/crud-typeorm.service.js.map +1 -0
- package/src/decorators/controller/types/index.d.ts +54 -0
- package/src/decorators/controller/types/index.js +6 -0
- package/src/decorators/controller/types/index.js.map +1 -0
- package/src/decorators/controller/utils/merge-options.d.ts +2 -0
- package/src/decorators/controller/utils/merge-options.js +37 -0
- package/src/decorators/controller/utils/merge-options.js.map +1 -0
- package/src/decorators/controller/utils/reflect.d.ts +10 -0
- package/src/decorators/controller/utils/reflect.js +86 -0
- package/src/decorators/controller/utils/reflect.js.map +1 -0
- package/src/decorators/prop.decorator.d.ts +9 -0
- package/src/decorators/prop.decorator.js +80 -0
- package/src/decorators/prop.decorator.js.map +1 -0
- package/src/exceptions/rpc-bad-request.exception.d.ts +4 -0
- package/src/exceptions/rpc-bad-request.exception.js +19 -0
- package/src/exceptions/rpc-bad-request.exception.js.map +1 -0
- package/src/exceptions/rpc-base.exception.d.ts +6 -0
- package/src/exceptions/rpc-base.exception.js +22 -0
- package/src/exceptions/rpc-base.exception.js.map +1 -0
- package/src/exceptions/rpc-conflict.exception.d.ts +4 -0
- package/src/exceptions/rpc-conflict.exception.js +19 -0
- package/src/exceptions/rpc-conflict.exception.js.map +1 -0
- package/src/exceptions/rpc-internal-server-error.exception.d.ts +4 -0
- package/src/exceptions/rpc-internal-server-error.exception.js +19 -0
- package/src/exceptions/rpc-internal-server-error.exception.js.map +1 -0
- package/src/exceptions/rpc-method-not-allowed.exception.d.ts +4 -0
- package/src/exceptions/rpc-method-not-allowed.exception.js +19 -0
- package/src/exceptions/rpc-method-not-allowed.exception.js.map +1 -0
- package/src/exceptions/rpc-not-found.exception.d.ts +4 -0
- package/src/exceptions/rpc-not-found.exception.js +19 -0
- package/src/exceptions/rpc-not-found.exception.js.map +1 -0
- package/src/exceptions/rpc-not-implemented.exception.d.ts +4 -0
- package/src/exceptions/rpc-not-implemented.exception.js +19 -0
- package/src/exceptions/rpc-not-implemented.exception.js.map +1 -0
- package/src/exceptions/rpc-unauthorized.exception.d.ts +4 -0
- package/src/exceptions/rpc-unauthorized.exception.js +19 -0
- package/src/exceptions/rpc-unauthorized.exception.js.map +1 -0
- package/src/filters/error-to-rpc-exception.filter.d.ts +4 -0
- package/src/filters/error-to-rpc-exception.filter.js +48 -0
- package/src/filters/error-to-rpc-exception.filter.js.map +1 -0
- package/src/filters/error-to-ws-exception.filter.d.ts +4 -0
- package/src/filters/error-to-ws-exception.filter.js +47 -0
- package/src/filters/error-to-ws-exception.filter.js.map +1 -0
- package/src/filters/typeorm-exception.filter.d.ts +6 -0
- package/src/filters/typeorm-exception.filter.js +44 -0
- package/src/filters/typeorm-exception.filter.js.map +1 -0
- package/src/filters/ws-exception.filter.d.ts +5 -0
- package/src/filters/ws-exception.filter.js +39 -0
- package/src/filters/ws-exception.filter.js.map +1 -0
- package/src/generate-metadata.d.ts +10 -0
- package/src/generate-metadata.js +97 -0
- package/src/generate-metadata.js.map +1 -0
- package/src/guards/auth.guard.d.ts +14 -0
- package/src/guards/auth.guard.js +92 -0
- package/src/guards/auth.guard.js.map +1 -0
- package/src/modules/basic/basic.controller.d.ts +3 -0
- package/src/modules/basic/basic.controller.js +33 -0
- package/src/modules/basic/basic.controller.js.map +1 -0
- package/src/modules/basic/basic.module.d.ts +2 -0
- package/src/modules/basic/basic.module.js +45 -0
- package/src/modules/basic/basic.module.js.map +1 -0
- package/src/modules/basic/basic.service.d.ts +2 -0
- package/src/modules/basic/basic.service.js +24 -0
- package/src/modules/basic/basic.service.js.map +1 -0
- package/src/nest-swagger-metadata.js +553 -0
- package/src/nest-swagger-metadata.js.map +1 -0
- package/src/utils/logger.d.ts +2 -0
- package/src/utils/logger.js +14 -0
- package/src/utils/logger.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/decorators/controller/dto/update-response.dto.ts"],"sourcesContent":["/**\n * a Dto for the response of the update or delete query\n */\nexport class UpdateResponseDto {\n /**\n * the total number of the affecter rows\n */\n affected: number;\n}\n"],"names":["UpdateResponseDto"],"mappings":"AAAA;;CAEC;;;;+BACYA;;;eAAAA;;;AAAN,IAAA,AAAMA,oBAAN,MAAMA;AAKb"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ControllerOptions } from '../types';
|
|
2
|
+
import { type CallHandler, type ExecutionContext, type NestInterceptor } from '@nestjs/common';
|
|
3
|
+
import { Observable } from 'rxjs';
|
|
4
|
+
export declare class QueryInterceptor implements NestInterceptor {
|
|
5
|
+
private readonly query;
|
|
6
|
+
constructor(query: ControllerOptions['query']);
|
|
7
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "QueryInterceptor", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return QueryInterceptor;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _common = require("@nestjs/common");
|
|
12
|
+
const _mergeanything = require("merge-anything");
|
|
13
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
14
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
15
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
16
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
17
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
18
|
+
}
|
|
19
|
+
function _ts_metadata(k, v) {
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
21
|
+
}
|
|
22
|
+
let QueryInterceptor = class QueryInterceptor {
|
|
23
|
+
intercept(context, next) {
|
|
24
|
+
let req = context.switchToHttp().getRequest();
|
|
25
|
+
// trying to parse JSON params
|
|
26
|
+
// client-side stringify JSON params using JSON.stringify
|
|
27
|
+
// for example `?where=JSON.stringify({field: "value"})`
|
|
28
|
+
for(let key in req.query){
|
|
29
|
+
if (typeof req.query[key] === 'string' && [
|
|
30
|
+
'[',
|
|
31
|
+
'{'
|
|
32
|
+
].includes(req.query[key][0])) {
|
|
33
|
+
try {
|
|
34
|
+
req.query[key] = JSON.parse(req.query[key]);
|
|
35
|
+
} catch {
|
|
36
|
+
// keep the value as string as-is
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// req.query is `Empty <[Object: null prototype] {}>` and not merged, but replaces ths.query
|
|
41
|
+
// https://github.com/mesqueeb/merge-anything/issues/48
|
|
42
|
+
req.query = (0, _mergeanything.merge)(this.query || {}, {
|
|
43
|
+
...req.query
|
|
44
|
+
});
|
|
45
|
+
return next.handle();
|
|
46
|
+
}
|
|
47
|
+
constructor(query){
|
|
48
|
+
this.query = query;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
QueryInterceptor = _ts_decorate([
|
|
52
|
+
(0, _common.Injectable)(),
|
|
53
|
+
_ts_metadata("design:type", Function),
|
|
54
|
+
_ts_metadata("design:paramtypes", [
|
|
55
|
+
Object
|
|
56
|
+
])
|
|
57
|
+
], QueryInterceptor);
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=query.interceptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/decorators/controller/interceptors/query.interceptor.ts"],"sourcesContent":["import type { ControllerOptions } from '../types';\nimport {\n type CallHandler,\n type ExecutionContext,\n Injectable,\n type NestInterceptor,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\n// @ts-ignore\nimport { merge } from 'merge-anything';\n\n/**\n * merge route query with the current request query.\n *\n * this ensures route.query{} is merged to the existing user routes too,\n * not only the routes that are generated by \\@Controller()\n */\n@Injectable()\nexport class QueryInterceptor implements NestInterceptor {\n constructor(private readonly query: ControllerOptions['query']) {}\n\n intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n let req = context.switchToHttp().getRequest();\n\n // trying to parse JSON params\n // client-side stringify JSON params using JSON.stringify\n // for example `?where=JSON.stringify({field: \"value\"})`\n for (let key in req.query) {\n if (\n typeof req.query[key] === 'string' &&\n ['[', '{'].includes(req.query[key][0])\n ) {\n try {\n req.query[key] = JSON.parse(req.query[key]);\n } catch {\n // keep the value as string as-is\n }\n }\n }\n\n // req.query is `Empty <[Object: null prototype] {}>` and not merged, but replaces ths.query\n // https://github.com/mesqueeb/merge-anything/issues/48\n req.query = merge(this.query || {}, { ...req.query });\n return next.handle();\n }\n}\n"],"names":["QueryInterceptor","intercept","context","next","req","switchToHttp","getRequest","key","query","includes","JSON","parse","merge","handle"],"mappings":";;;;+BAkBaA;;;eAAAA;;;wBAZN;+BAGe;;;;;;;;;;AASf,IAAA,AAAMA,mBAAN,MAAMA;IAGXC,UAAUC,OAAyB,EAAEC,IAAiB,EAAmB;QACvE,IAAIC,MAAMF,QAAQG,YAAY,GAAGC,UAAU;QAE3C,8BAA8B;QAC9B,yDAAyD;QACzD,wDAAwD;QACxD,IAAK,IAAIC,OAAOH,IAAII,KAAK,CAAE;YACzB,IACE,OAAOJ,IAAII,KAAK,CAACD,IAAI,KAAK,YAC1B;gBAAC;gBAAK;aAAI,CAACE,QAAQ,CAACL,IAAII,KAAK,CAACD,IAAI,CAAC,EAAE,GACrC;gBACA,IAAI;oBACFH,IAAII,KAAK,CAACD,IAAI,GAAGG,KAAKC,KAAK,CAACP,IAAII,KAAK,CAACD,IAAI;gBAC5C,EAAE,OAAM;gBACN,iCAAiC;gBACnC;YACF;QACF;QAEA,4FAA4F;QAC5F,uDAAuD;QACvDH,IAAII,KAAK,GAAGI,IAAAA,oBAAK,EAAC,IAAI,CAACJ,KAAK,IAAI,CAAC,GAAG;YAAE,GAAGJ,IAAII,KAAK;QAAC;QACnD,OAAOL,KAAKU,MAAM;IACpB;IAzBA,YAAY,AAAiBL,KAAiC,CAAE;aAAnCA,QAAAA;IAAoC;AA0BnE"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "routeHandler", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return routeHandler;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _common = require("@nestjs/common");
|
|
12
|
+
function routeHandler(route, factory) {
|
|
13
|
+
let serviceName = factory.options?.serviceName || 'service';
|
|
14
|
+
// don't convert this to arrow function, so `this` here refers to the Controller class
|
|
15
|
+
// don't use factory.controller.prototype[serviceName] as service will be undefined
|
|
16
|
+
// todo: query type
|
|
17
|
+
return function(req, query, ...params) {
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
let service = this[serviceName];
|
|
20
|
+
if (!service) {
|
|
21
|
+
throw new _common.HttpException(`this.${serviceName} is not defined, please inject the service`, 500);
|
|
22
|
+
}
|
|
23
|
+
if (!service[route.methodName]) {
|
|
24
|
+
throw new _common.HttpException(`the method this.${serviceName}.${route.methodName}() is not defined in the service`, 500);
|
|
25
|
+
} else if (typeof service[route.methodName] !== 'function') {
|
|
26
|
+
throw new _common.HttpException(`this.${serviceName}.${route.methodName} is not a method`, 500);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
// todo: send the proper params to each operation
|
|
30
|
+
// getOne(id), postOne(body), patchOne(id,body)
|
|
31
|
+
if (factory.options.maxLimit && query.take > factory.options.maxLimit) {
|
|
32
|
+
query.take = factory.options.maxLimit;
|
|
33
|
+
}
|
|
34
|
+
return service[route.methodName](...params, query, req);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// todo: get class name using Reflect
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.error(`error at ${factory.controller.name}.${route.methodName}()`, error);
|
|
39
|
+
throw new _common.HttpException(error?.message || error, 500);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=route-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/decorators/controller/route-handler.ts"],"sourcesContent":["import { ControllerFactory } from './controller.factory';\nimport type { IRoute } from './types';\nimport { HttpException } from '@nestjs/common';\n\n/**\n * returns a route handler that receives the Request object as its first arg\n */\nexport function routeHandler(route: IRoute, factory: ControllerFactory) {\n let serviceName = factory.options?.serviceName || 'service';\n // don't convert this to arrow function, so `this` here refers to the Controller class\n // don't use factory.controller.prototype[serviceName] as service will be undefined\n // todo: query type\n return function (req: Request, query: any, ...params: any) {\n // @ts-ignore\n let service = this[serviceName];\n\n if (!service) {\n throw new HttpException(\n `this.${serviceName} is not defined, please inject the service`,\n 500,\n );\n }\n\n if (!service[route.methodName]) {\n throw new HttpException(\n `the method this.${serviceName}.${route.methodName}() is not defined in the service`,\n 500,\n );\n } else if (typeof service[route.methodName] !== 'function') {\n throw new HttpException(\n `this.${serviceName}.${route.methodName} is not a method`,\n 500,\n );\n }\n\n try {\n // todo: send the proper params to each operation\n // getOne(id), postOne(body), patchOne(id,body)\n if (factory.options.maxLimit && query.take > factory.options.maxLimit) {\n query.take = factory.options.maxLimit;\n }\n return service[route.methodName](...params, query, req);\n } catch (error: any) {\n // todo: get class name using Reflect\n // eslint-disable-next-line no-console\n console.error(\n `error at ${factory.controller.name}.${route.methodName}()`,\n error,\n );\n throw new HttpException(error?.message || error, 500);\n }\n };\n}\n"],"names":["routeHandler","route","factory","serviceName","options","req","query","params","service","HttpException","methodName","maxLimit","take","error","console","controller","name","message"],"mappings":";;;;+BAOgBA;;;eAAAA;;;wBALc;AAKvB,SAASA,aAAaC,KAAa,EAAEC,OAA0B;IACpE,IAAIC,cAAcD,QAAQE,OAAO,EAAED,eAAe;IAClD,sFAAsF;IACtF,mFAAmF;IACnF,mBAAmB;IACnB,OAAO,SAAUE,GAAY,EAAEC,KAAU,EAAE,GAAGC,MAAW;QACvD,aAAa;QACb,IAAIC,UAAU,IAAI,CAACL,YAAY;QAE/B,IAAI,CAACK,SAAS;YACZ,MAAM,IAAIC,qBAAa,CACrB,CAAC,KAAK,EAAEN,YAAY,0CAA0C,CAAC,EAC/D;QAEJ;QAEA,IAAI,CAACK,OAAO,CAACP,MAAMS,UAAU,CAAC,EAAE;YAC9B,MAAM,IAAID,qBAAa,CACrB,CAAC,gBAAgB,EAAEN,YAAY,CAAC,EAAEF,MAAMS,UAAU,CAAC,gCAAgC,CAAC,EACpF;QAEJ,OAAO,IAAI,OAAOF,OAAO,CAACP,MAAMS,UAAU,CAAC,KAAK,YAAY;YAC1D,MAAM,IAAID,qBAAa,CACrB,CAAC,KAAK,EAAEN,YAAY,CAAC,EAAEF,MAAMS,UAAU,CAAC,gBAAgB,CAAC,EACzD;QAEJ;QAEA,IAAI;YACF,iDAAiD;YACjD,+CAA+C;YAC/C,IAAIR,QAAQE,OAAO,CAACO,QAAQ,IAAIL,MAAMM,IAAI,GAAGV,QAAQE,OAAO,CAACO,QAAQ,EAAE;gBACrEL,MAAMM,IAAI,GAAGV,QAAQE,OAAO,CAACO,QAAQ;YACvC;YACA,OAAOH,OAAO,CAACP,MAAMS,UAAU,CAAC,IAAIH,QAAQD,OAAOD;QACrD,EAAE,OAAOQ,OAAY;YACnB,qCAAqC;YACrC,sCAAsC;YACtCC,QAAQD,KAAK,CACX,CAAC,SAAS,EAAEX,QAAQa,UAAU,CAACC,IAAI,CAAC,CAAC,EAAEf,MAAMS,UAAU,CAAC,EAAE,CAAC,EAC3DG;YAEF,MAAM,IAAIJ,qBAAa,CAACI,OAAOI,WAAWJ,OAAO;QACnD;IACF;AACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "Route", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return Route;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
function Route(route) {
|
|
12
|
+
return (target, property)=>{
|
|
13
|
+
Reflect.defineMetadata('routeOptions', route, target, property);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=route.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/decorators/controller/route.decorator.ts"],"sourcesContent":["import { InitRoute } from './types';\n\n/**\n * add route options to the method to override the options set by `@Controller()`\n *\n * @example\n * ```\n * \\@Route({httpMethod: 'GET', many: true})\n * getAllUsers(){\n * return this.service.getAllUsers();\n * }\n * ```\n */\nexport function Route(route?: InitRoute): MethodDecorator {\n return (target: Object, property: string | symbol) => {\n Reflect.defineMetadata('routeOptions', route, target, property);\n };\n}\n"],"names":["Route","route","target","property","Reflect","defineMetadata"],"mappings":";;;;+BAagBA;;;eAAAA;;;AAAT,SAASA,MAAMC,KAAiB;IACrC,OAAO,CAACC,QAAgBC;QACtBC,QAAQC,cAAc,CAAC,gBAAgBJ,OAAOC,QAAQC;IACxD;AACF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type FindManyOptions, type ObjectLiteral, Repository } from 'typeorm';
|
|
2
|
+
import type { DeepPartial } from 'typeorm/common/DeepPartial';
|
|
3
|
+
export interface IQuery<Entity = any> extends Omit<FindManyOptions<Entity>, 'where' | 'relations'> {
|
|
4
|
+
where?: FindManyOptions['where'] | string;
|
|
5
|
+
relations?: FindManyOptions['relations'] | string;
|
|
6
|
+
}
|
|
7
|
+
export declare class CrudTypeOrmService<Entity extends ObjectLiteral> {
|
|
8
|
+
protected repo: Repository<Entity>;
|
|
9
|
+
constructor(repo: Repository<Entity>);
|
|
10
|
+
createQuery<Entity>(query?: IQuery<Entity>, primaryKeyValue?: string | number, primaryKeyName?: string): FindManyOptions<Entity>;
|
|
11
|
+
getOne(id: any, query?: any, _req?: Request): Promise<Entity | null>;
|
|
12
|
+
getMany(query?: any, _req?: Request): Promise<{
|
|
13
|
+
data: Entity[];
|
|
14
|
+
count: number;
|
|
15
|
+
}>;
|
|
16
|
+
getManyByRelation(field: string, value: any, query?: any, primaryKeyName?: string, req?: Request): Promise<{
|
|
17
|
+
data: Entity[];
|
|
18
|
+
count: number;
|
|
19
|
+
}>;
|
|
20
|
+
post(body: DeepPartial<Entity>, _query?: any, _req?: Request): Promise<DeepPartial<Entity> & Entity>;
|
|
21
|
+
patchOne(body: DeepPartial<Entity>, primaryKey?: string | number | {
|
|
22
|
+
[key: string]: any;
|
|
23
|
+
}, _req?: Request): Promise<(Entity & {
|
|
24
|
+
id: any;
|
|
25
|
+
}) | ((Entity extends (infer U)[] ? DeepPartial<U>[] : Entity extends Map<infer K, infer V> ? Map<DeepPartial<K>, DeepPartial<V>> : Entity extends Set<infer M> ? Set<DeepPartial<M>> : Entity extends object ? { [K_1 in keyof Entity]?: DeepPartial<Entity[K_1]> | undefined; } : Entity) & {
|
|
26
|
+
id: any;
|
|
27
|
+
} & Entity)>;
|
|
28
|
+
patchMany(body: DeepPartial<Entity>, query?: any, _req?: Request): Promise<((Entity & {
|
|
29
|
+
id: any;
|
|
30
|
+
}) | ((Entity extends (infer U)[] ? DeepPartial<U>[] : Entity extends Map<infer K, infer V> ? Map<DeepPartial<K>, DeepPartial<V>> : Entity extends Set<infer M> ? Set<DeepPartial<M>> : Entity extends object ? { [K_1 in keyof Entity]?: DeepPartial<Entity[K_1]> | undefined; } : Entity) & {
|
|
31
|
+
id: any;
|
|
32
|
+
} & Entity))[]>;
|
|
33
|
+
putOne(): void;
|
|
34
|
+
putMany(): void;
|
|
35
|
+
deleteOne(primaryKeyOrCondition: string | number | {
|
|
36
|
+
[key: string]: any;
|
|
37
|
+
}, query?: any, req?: Request): Promise<{
|
|
38
|
+
affected: number | null | undefined;
|
|
39
|
+
}>;
|
|
40
|
+
deleteMany(query?: any, _req?: Request): Promise<{
|
|
41
|
+
affected: number | null | undefined;
|
|
42
|
+
}>;
|
|
43
|
+
clear(): Promise<void>;
|
|
44
|
+
toArray(value?: any, delimiter?: string): any;
|
|
45
|
+
toObject(value?: any, defaultValue?: any): any;
|
|
46
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "CrudTypeOrmService", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return CrudTypeOrmService;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _common = require("@nestjs/common");
|
|
12
|
+
const _typeorm = require("typeorm");
|
|
13
|
+
const _mergeanything = require("merge-anything");
|
|
14
|
+
let CrudTypeOrmService = class CrudTypeOrmService {
|
|
15
|
+
/**
|
|
16
|
+
* create a TypeORM Query object
|
|
17
|
+
* @param query
|
|
18
|
+
* @param primaryKey the primary key value, for "*One" operations
|
|
19
|
+
* @param primaryKeyName the primary key field name, default: "id"
|
|
20
|
+
*/ // todo: enforce the return type
|
|
21
|
+
// todo: `T extends FindOneOptions | FindManyOptions | ...`
|
|
22
|
+
createQuery(query, primaryKeyValue, primaryKeyName = 'id') {
|
|
23
|
+
query = {
|
|
24
|
+
...query,
|
|
25
|
+
// for "one" operations, include deleted records by default
|
|
26
|
+
withDeleted: primaryKeyValue ? query?.withDeleted !== false : query?.withDeleted,
|
|
27
|
+
// todo: nested relations "users.profiles"
|
|
28
|
+
// todo: relation fields "users:name,email"
|
|
29
|
+
// todo: relations may be an object rather than an array
|
|
30
|
+
relations: this.toArray(query?.relations),
|
|
31
|
+
select: this.toArray(query?.select),
|
|
32
|
+
order: this.toObject(query?.order, 'ASC')
|
|
33
|
+
};
|
|
34
|
+
if (query.where) {
|
|
35
|
+
// convert query.where to object
|
|
36
|
+
if (typeof query.where === 'string' && // json format
|
|
37
|
+
(query.where.startsWith('{') || // query.where can be a string, i.e. "primaryKey"
|
|
38
|
+
// if its format is not `key:value, key2` keep it string
|
|
39
|
+
// if it is a single text value, consider it as primaryKey value
|
|
40
|
+
// matches: `a:1, b:2, c` and `a,b` but not `id`
|
|
41
|
+
// todo: deprecate inline k-v syntax, use explicit object
|
|
42
|
+
// eslint-disable-next-line regexp/optimal-quantifier-concatenation
|
|
43
|
+
/(.+:.+(,.+)?)|(.+,.+)/.test(query.where))) {
|
|
44
|
+
query.where = this.toObject(query.where);
|
|
45
|
+
}
|
|
46
|
+
// todo: if isObject()
|
|
47
|
+
if (typeof query.where !== 'string' && !Array.isArray(query.where)) {
|
|
48
|
+
Object.keys(query.where).map((key)=>{
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
let value = query.where[key];
|
|
51
|
+
if (Array.isArray(value)) {
|
|
52
|
+
// check if the array column contains the value
|
|
53
|
+
// todo: use the entity to check if the field type is an array
|
|
54
|
+
// @ts-ignore
|
|
55
|
+
query.where[key] = (0, _typeorm.ArrayContains)(value.map((el)=>el?.trim()));
|
|
56
|
+
} else if (typeof value === 'string') {
|
|
57
|
+
value = value?.trim();
|
|
58
|
+
// if the value starts with ">", consider it as a raw sql statement
|
|
59
|
+
// convert `{key: value}` to `{key: Raw(field=>value)}`
|
|
60
|
+
// to enable sql syntax and find operations
|
|
61
|
+
// such as: `Like '%test%'`
|
|
62
|
+
// todo: check for sql injection of the user's input
|
|
63
|
+
// todo: if it includes a find operation such as `Like` (can be a part of the value)
|
|
64
|
+
// example `{key: "> {field} > 0"}`
|
|
65
|
+
if (value?.startsWith?.('>')) {
|
|
66
|
+
// @ts-ignore
|
|
67
|
+
query.where[key] = (0, _typeorm.Raw)((field)=>`${value.slice(1)}`.replaceAll('{field}', field));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// add primaryKey to the condition
|
|
72
|
+
if (primaryKeyValue && primaryKeyName) {
|
|
73
|
+
query.where = {
|
|
74
|
+
[primaryKeyName]: primaryKeyValue,
|
|
75
|
+
...query.where
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} else if (primaryKeyValue) {
|
|
80
|
+
query.where = {
|
|
81
|
+
[primaryKeyName]: primaryKeyValue
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return query;
|
|
85
|
+
}
|
|
86
|
+
// todo: rename to getOneById() or getOneBy${primaryParam}
|
|
87
|
+
// todo: route.paramKey{name, type:String|Number}
|
|
88
|
+
// todo: types
|
|
89
|
+
// todo: parse query params, and add type ControllerOptions.QueryParams
|
|
90
|
+
getOne(id, query, _req) {
|
|
91
|
+
return this.repo.findOne(this.createQuery(query, id));
|
|
92
|
+
}
|
|
93
|
+
// todo: handle operations, example: `{where: {email: "like %john%"} }`
|
|
94
|
+
getMany(query, _req) {
|
|
95
|
+
return this.repo.findAndCount(this.createQuery(query)).then((res)=>({
|
|
96
|
+
data: res[0],
|
|
97
|
+
count: res[1]
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* get records by a relation field
|
|
102
|
+
* @param field the relationship field name
|
|
103
|
+
* @param value
|
|
104
|
+
* @param query
|
|
105
|
+
* @param primaryKeyName
|
|
106
|
+
* @param req
|
|
107
|
+
* @returns
|
|
108
|
+
* @example
|
|
109
|
+
* if the table posts has a relation with users,
|
|
110
|
+
* we can get all posts by the user by `getManyByRelation("user", 1)`
|
|
111
|
+
*/ // todo: merge options.query{}
|
|
112
|
+
// for example the following route should inherit options from @Controller()
|
|
113
|
+
// @Controller(options)
|
|
114
|
+
// class MyController{
|
|
115
|
+
// @Get('/users/:userId/posts')
|
|
116
|
+
// getUserPosts(){
|
|
117
|
+
// return this.service.getManyByRelation(...) // options.query from @Controller() should be merged here
|
|
118
|
+
// }
|
|
119
|
+
// }
|
|
120
|
+
getManyByRelation(field, value, query, primaryKeyName = 'id', req) {
|
|
121
|
+
return this.getMany((0, _mergeanything.merge)(query || {}, {
|
|
122
|
+
where: {
|
|
123
|
+
[field]: {
|
|
124
|
+
[primaryKeyName]: value
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}), req);
|
|
128
|
+
}
|
|
129
|
+
post(body, _query, _req) {
|
|
130
|
+
// .save() automatically saves the manyToMany relations if cascade=true
|
|
131
|
+
// or this.repo.insert(body).then((res) => (Array.isArray(body) ? res?.raw : res?.raw[0]))
|
|
132
|
+
return this.repo.save(body);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* update a record by its primary key
|
|
136
|
+
*/ patchOne(// todo: add type opts.model
|
|
137
|
+
body, primaryKey = 'id', _req) {
|
|
138
|
+
return this.getOne(primaryKey).then((res)=>{
|
|
139
|
+
if (!res?.id) throw new Error(`not found`);
|
|
140
|
+
// .save() automatically modifies the ManyToMany relations
|
|
141
|
+
// but requires fetching the items to be modified to add the id of each item
|
|
142
|
+
// otherwise it will create a new item if it doesn't have an id
|
|
143
|
+
return this.repo.save({
|
|
144
|
+
...body,
|
|
145
|
+
id: res.id
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
patchMany(body, query, _req) {
|
|
150
|
+
return this.repo.find(query).then((res)=>res.map((el)=>({
|
|
151
|
+
...body,
|
|
152
|
+
id: el.id
|
|
153
|
+
}))).then((res)=>this.repo.save(res));
|
|
154
|
+
// .update() doesn't save ManyToMany relation automatically
|
|
155
|
+
// but .save() doesn't have where condition (each item has to specify id)
|
|
156
|
+
// and has no limit option (it relies on the number of the items being updated)
|
|
157
|
+
// use .save() see patchOne(), it meeds merging body[] with existing ids of the items to be updated
|
|
158
|
+
// or create a route to save (insert or update) items `POST /save`
|
|
159
|
+
// return this.repo
|
|
160
|
+
// .update(query.where, body)
|
|
161
|
+
// .then((res) => ({ affected: res.affected }));
|
|
162
|
+
}
|
|
163
|
+
putOne() {
|
|
164
|
+
throw new _common.NotImplementedException();
|
|
165
|
+
}
|
|
166
|
+
putMany() {
|
|
167
|
+
throw new _common.NotImplementedException();
|
|
168
|
+
}
|
|
169
|
+
deleteOne(primaryKeyOrCondition, query = {}, req) {
|
|
170
|
+
query.where = [
|
|
171
|
+
'string',
|
|
172
|
+
'number'
|
|
173
|
+
].includes(typeof primaryKeyOrCondition) ? primaryKeyOrCondition : {
|
|
174
|
+
...query.where,
|
|
175
|
+
...primaryKeyOrCondition
|
|
176
|
+
};
|
|
177
|
+
return this.deleteMany(query, req);
|
|
178
|
+
}
|
|
179
|
+
deleteMany(query, _req) {
|
|
180
|
+
// todo: softDelete() vs softRemove()
|
|
181
|
+
let deleteQuery;
|
|
182
|
+
// repo.delete({}) no longer allowed by TypeORM, in this case we'll use QueryBuilder to provide a fake criteria
|
|
183
|
+
if (query.where) {
|
|
184
|
+
deleteQuery = query.softDelete === false ? this.repo.delete(query.where) : this.repo.softDelete(query.where);
|
|
185
|
+
} else {
|
|
186
|
+
let builder = this.repo.createQueryBuilder();
|
|
187
|
+
deleteQuery = query.softDelete === false ? builder.delete().where('1 = 1').execute() : builder.softDelete().where('1 = 1').execute();
|
|
188
|
+
}
|
|
189
|
+
return deleteQuery.then((res)=>({
|
|
190
|
+
affected: res?.affected
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* delete all data using `TRUNCATE` instead of `DELETE`
|
|
195
|
+
*/ // todo: add a route to clear (truncate) the data, for instance `DELETE <entity>/clear`
|
|
196
|
+
clear() {
|
|
197
|
+
return this.repo.clear();
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* convert delimited string int array items and remove empty items
|
|
201
|
+
* @example 'a,b,c' => ['a', 'b', 'c']
|
|
202
|
+
*/ toArray(value, delimiter = ',') {
|
|
203
|
+
return typeof value === 'string' ? value?.split?.(delimiter)?.filter((el)=>el && el.trim() !== '') : value;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* convert JSON string or delimited string into a plain object
|
|
207
|
+
* @example `{"a": 1}` => `{a: 1}`
|
|
208
|
+
* @example `a:1, b:2, c` => `{a: 1, b: 2, c: defaultValue}`
|
|
209
|
+
*/ toObject(value, defaultValue = true) {
|
|
210
|
+
if (!value) return;
|
|
211
|
+
if (typeof value !== 'string') return value;
|
|
212
|
+
// todo: if isObject(value) return value
|
|
213
|
+
try {
|
|
214
|
+
return JSON.parse(value);
|
|
215
|
+
} catch {
|
|
216
|
+
return value?.split?.(',')?.reduce?.((acc, el)=>{
|
|
217
|
+
let [key, value = defaultValue] = el.split(':');
|
|
218
|
+
return {
|
|
219
|
+
...acc,
|
|
220
|
+
[key.trim()]: value.trim()
|
|
221
|
+
};
|
|
222
|
+
}, {});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
constructor(// todo: inject repo using Entity
|
|
226
|
+
// @InjectRepository(Dto) private repo: Repository<Entity>,
|
|
227
|
+
repo){
|
|
228
|
+
this.repo = repo;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
//# sourceMappingURL=crud-typeorm.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/decorators/controller/services/crud-typeorm.service.ts"],"sourcesContent":["import { NotImplementedException } from '@nestjs/common';\nimport {\n ArrayContains,\n DeleteResult,\n type FindManyOptions,\n type ObjectLiteral,\n Raw,\n Repository,\n UpdateResult,\n} from 'typeorm';\nimport type { DeepPartial } from 'typeorm/common/DeepPartial';\n// @ts-ignore\nimport { merge } from 'merge-anything';\n\n/**\n * find options but some props accepts string format that parsed internally into the correct format\n * for example { where: \"age>30,name='john'\" }\n */\nexport interface IQuery<Entity = any> extends Omit<\n FindManyOptions<Entity>,\n 'where' | 'relations'\n> {\n where?: FindManyOptions['where'] | string;\n relations?: FindManyOptions['relations'] | string;\n}\n\n/**\n * The CRUD implementation service for TypeORM\n * it adds methods such as `getOne()`, `getMany()`, and `post()` that ue TypeORM repo\n * to work with the data model\n */\n// todo: add pipes, example: `update(@Param('id', ParseUUIDPipe) id: string, body){}`\nexport class CrudTypeOrmService<Entity extends ObjectLiteral> {\n constructor(\n // todo: inject repo using Entity\n // @InjectRepository(Dto) private repo: Repository<Entity>,\n protected repo: Repository<Entity>,\n ) {}\n\n /**\n * create a TypeORM Query object\n * @param query\n * @param primaryKey the primary key value, for \"*One\" operations\n * @param primaryKeyName the primary key field name, default: \"id\"\n */\n // todo: enforce the return type\n // todo: `T extends FindOneOptions | FindManyOptions | ...`\n createQuery<Entity>(\n query?: IQuery<Entity>,\n primaryKeyValue?: string | number,\n primaryKeyName = 'id', // FindManyOptions already extends FindOneOptions\n ): FindManyOptions<Entity> {\n query = {\n ...query,\n // for \"one\" operations, include deleted records by default\n withDeleted: primaryKeyValue\n ? query?.withDeleted !== false\n : query?.withDeleted,\n // todo: nested relations \"users.profiles\"\n // todo: relation fields \"users:name,email\"\n // todo: relations may be an object rather than an array\n relations: this.toArray(query?.relations),\n select: this.toArray(query?.select),\n order: this.toObject(query?.order, 'ASC'),\n };\n\n if (query.where) {\n // convert query.where to object\n if (\n typeof query.where === 'string' &&\n // json format\n (query.where.startsWith('{') ||\n // query.where can be a string, i.e. \"primaryKey\"\n // if its format is not `key:value, key2` keep it string\n // if it is a single text value, consider it as primaryKey value\n // matches: `a:1, b:2, c` and `a,b` but not `id`\n // todo: deprecate inline k-v syntax, use explicit object\n // eslint-disable-next-line regexp/optimal-quantifier-concatenation\n /(.+:.+(,.+)?)|(.+,.+)/.test(query.where))\n ) {\n query.where = this.toObject(query.where);\n }\n\n // todo: if isObject()\n if (typeof query.where !== 'string' && !Array.isArray(query.where)) {\n Object.keys(query.where!).map((key) => {\n // @ts-ignore\n let value = query.where[key];\n if (Array.isArray(value)) {\n // check if the array column contains the value\n // todo: use the entity to check if the field type is an array\n // @ts-ignore\n query.where[key] = ArrayContains(value.map((el) => el?.trim()));\n } else if (typeof value === 'string') {\n value = value?.trim();\n\n // if the value starts with \">\", consider it as a raw sql statement\n // convert `{key: value}` to `{key: Raw(field=>value)}`\n // to enable sql syntax and find operations\n // such as: `Like '%test%'`\n // todo: check for sql injection of the user's input\n // todo: if it includes a find operation such as `Like` (can be a part of the value)\n // example `{key: \"> {field} > 0\"}`\n if (value?.startsWith?.('>')) {\n // @ts-ignore\n query.where[key] = Raw((field: string) =>\n `${value.slice(1)}`.replaceAll('{field}', field),\n );\n }\n }\n });\n\n // add primaryKey to the condition\n if (primaryKeyValue && primaryKeyName) {\n query.where = {\n [primaryKeyName]: primaryKeyValue,\n ...query.where,\n };\n }\n }\n } else if (primaryKeyValue) {\n query.where = { [primaryKeyName]: primaryKeyValue };\n }\n\n return <FindManyOptions<Entity>>query;\n }\n\n // todo: rename to getOneById() or getOneBy${primaryParam}\n // todo: route.paramKey{name, type:String|Number}\n // todo: types\n // todo: parse query params, and add type ControllerOptions.QueryParams\n getOne(id: any, query?: any, _req?: Request) {\n return this.repo.findOne(this.createQuery(query, id));\n }\n\n // todo: handle operations, example: `{where: {email: \"like %john%\"} }`\n getMany(query?: any, _req?: Request) {\n return this.repo\n .findAndCount(this.createQuery(query))\n .then((res) => ({ data: res[0], count: res[1] }));\n }\n\n /**\n * get records by a relation field\n * @param field the relationship field name\n * @param value\n * @param query\n * @param primaryKeyName\n * @param req\n * @returns\n * @example\n * if the table posts has a relation with users,\n * we can get all posts by the user by `getManyByRelation(\"user\", 1)`\n */\n // todo: merge options.query{}\n // for example the following route should inherit options from @Controller()\n // @Controller(options)\n // class MyController{\n // @Get('/users/:userId/posts')\n // getUserPosts(){\n // return this.service.getManyByRelation(...) // options.query from @Controller() should be merged here\n // }\n // }\n getManyByRelation(\n field: string,\n value: any,\n query?: any,\n primaryKeyName = 'id',\n req?: Request,\n ) {\n return this.getMany(\n merge(query || {}, { where: { [field]: { [primaryKeyName]: value } } }),\n req,\n );\n }\n\n post(body: DeepPartial<Entity>, _query?: any, _req?: Request) {\n // .save() automatically saves the manyToMany relations if cascade=true\n // or this.repo.insert(body).then((res) => (Array.isArray(body) ? res?.raw : res?.raw[0]))\n return this.repo.save(body);\n }\n\n /**\n * update a record by its primary key\n */\n patchOne(\n // todo: add type opts.model\n body: DeepPartial<Entity>,\n primaryKey: string | number | { [key: string]: any } = 'id',\n _req?: Request,\n ) {\n return this.getOne(primaryKey).then((res) => {\n if (!res?.id) throw new Error(`not found`);\n // .save() automatically modifies the ManyToMany relations\n // but requires fetching the items to be modified to add the id of each item\n // otherwise it will create a new item if it doesn't have an id\n return this.repo.save({ ...body, id: res.id });\n });\n }\n\n patchMany(body: DeepPartial<Entity>, query?: any, _req?: Request) {\n return this.repo\n .find(query)\n .then((res) => res.map((el) => ({ ...body, id: el.id })))\n .then((res) => this.repo.save(res));\n\n // .update() doesn't save ManyToMany relation automatically\n // but .save() doesn't have where condition (each item has to specify id)\n // and has no limit option (it relies on the number of the items being updated)\n // use .save() see patchOne(), it meeds merging body[] with existing ids of the items to be updated\n // or create a route to save (insert or update) items `POST /save`\n\n // return this.repo\n // .update(query.where, body)\n // .then((res) => ({ affected: res.affected }));\n }\n\n putOne() {\n throw new NotImplementedException();\n }\n\n putMany() {\n throw new NotImplementedException();\n }\n\n deleteOne(\n primaryKeyOrCondition: string | number | { [key: string]: any },\n query: any = {},\n req?: Request,\n ) {\n query.where = ['string', 'number'].includes(typeof primaryKeyOrCondition)\n ? primaryKeyOrCondition\n : { ...query.where, ...(<{ [key: string]: any }>primaryKeyOrCondition) };\n return this.deleteMany(query, req);\n }\n\n deleteMany(query?: any, _req?: Request) {\n // todo: softDelete() vs softRemove()\n\n let deleteQuery: Promise<DeleteResult | UpdateResult>;\n // repo.delete({}) no longer allowed by TypeORM, in this case we'll use QueryBuilder to provide a fake criteria\n if (query.where) {\n deleteQuery =\n query.softDelete === false\n ? this.repo.delete(query.where)\n : this.repo.softDelete(query.where);\n } else {\n let builder = this.repo.createQueryBuilder();\n deleteQuery =\n query.softDelete === false\n ? builder.delete().where('1 = 1').execute()\n : builder.softDelete().where('1 = 1').execute();\n }\n return deleteQuery.then((res) => ({ affected: res?.affected }));\n }\n\n /**\n * delete all data using `TRUNCATE` instead of `DELETE`\n */\n // todo: add a route to clear (truncate) the data, for instance `DELETE <entity>/clear`\n clear() {\n return this.repo.clear();\n }\n\n /**\n * convert delimited string int array items and remove empty items\n * @example 'a,b,c' => ['a', 'b', 'c']\n */\n toArray(value?: any, delimiter = ',') {\n return typeof value === 'string'\n ? value\n ?.split?.(delimiter)\n // remove empty elements\n ?.filter((el) => el && el.trim() !== '')\n : value;\n }\n\n /**\n * convert JSON string or delimited string into a plain object\n * @example `{\"a\": 1}` => `{a: 1}`\n * @example `a:1, b:2, c` => `{a: 1, b: 2, c: defaultValue}`\n */\n toObject(value?: any, defaultValue: any = true) {\n if (!value) return;\n if (typeof value !== 'string') return value;\n // todo: if isObject(value) return value\n\n try {\n return JSON.parse(value);\n } catch {\n return value?.split?.(',')?.reduce?.(\n (acc, el) => {\n let [key, value = defaultValue] = el.split(':');\n return { ...acc, [key.trim()]: value.trim() };\n },\n <{ [key: string]: any }>{},\n );\n }\n }\n}\n"],"names":["CrudTypeOrmService","createQuery","query","primaryKeyValue","primaryKeyName","withDeleted","relations","toArray","select","order","toObject","where","startsWith","test","Array","isArray","Object","keys","map","key","value","ArrayContains","el","trim","Raw","field","slice","replaceAll","getOne","id","_req","repo","findOne","getMany","findAndCount","then","res","data","count","getManyByRelation","req","merge","post","body","_query","save","patchOne","primaryKey","Error","patchMany","find","putOne","NotImplementedException","putMany","deleteOne","primaryKeyOrCondition","includes","deleteMany","deleteQuery","softDelete","delete","builder","createQueryBuilder","execute","affected","clear","delimiter","split","filter","defaultValue","JSON","parse","reduce","acc"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { HttpStatus, type NestInterceptor, type Type } from '@nestjs/common';
|
|
2
|
+
import type { ApiOperationOptions, ApiParamOptions } from '@nestjs/swagger';
|
|
3
|
+
import type { ParameterObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface';
|
|
4
|
+
import type { SwaggerEnumType } from '@nestjs/swagger/dist/types/swagger-enum.type';
|
|
5
|
+
type MakeRequired<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
|
|
6
|
+
export interface ControllerOptions extends MakeRequired<InitControllerOptions, 'serviceName' | 'routes'> {
|
|
7
|
+
routes: IRoute[];
|
|
8
|
+
}
|
|
9
|
+
export interface IRoute extends MakeRequired<InitRoute, 'methodName' | 'httpMethod' | 'decorators' | 'interceptors'> {
|
|
10
|
+
}
|
|
11
|
+
export interface InitControllerOptions {
|
|
12
|
+
path?: string;
|
|
13
|
+
routes?: InitRoute[];
|
|
14
|
+
queryParams?: QueryParam[];
|
|
15
|
+
serviceName?: string;
|
|
16
|
+
model?: any;
|
|
17
|
+
responseModel?: InitControllerOptions['model'];
|
|
18
|
+
errorResponseModel?: InitControllerOptions['model'];
|
|
19
|
+
responseModels?: Array<{
|
|
20
|
+
status: HttpStatus;
|
|
21
|
+
type: InitControllerOptions['model'];
|
|
22
|
+
}>;
|
|
23
|
+
primaryKey?: string;
|
|
24
|
+
maxLimit?: number;
|
|
25
|
+
decorators?: (MethodDecorator | PropertyDecorator)[];
|
|
26
|
+
interceptors?: (NestInterceptor | Function)[];
|
|
27
|
+
query?: {
|
|
28
|
+
[key: string]: any;
|
|
29
|
+
};
|
|
30
|
+
tags?: string | string | null;
|
|
31
|
+
}
|
|
32
|
+
export interface InitRoute extends ApiOperationOptions, Omit<InitControllerOptions, 'routes' | 'tags'> {
|
|
33
|
+
httpMethod?: HttpMethod;
|
|
34
|
+
path?: string;
|
|
35
|
+
methodName?: string;
|
|
36
|
+
many?: boolean;
|
|
37
|
+
methodFactory?: () => any;
|
|
38
|
+
description?: string;
|
|
39
|
+
pathParams?: ApiParamOptions[];
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface QueryParam extends ParameterOptions {
|
|
43
|
+
name?: string;
|
|
44
|
+
type?: Type<unknown> | Function | [Function] | string;
|
|
45
|
+
isArray?: boolean;
|
|
46
|
+
enum?: SwaggerEnumType;
|
|
47
|
+
enumName?: string;
|
|
48
|
+
many?: boolean;
|
|
49
|
+
httpMethods?: HttpMethod[];
|
|
50
|
+
}
|
|
51
|
+
type ParameterOptions = Omit<ParameterObject, 'in' | 'name'>;
|
|
52
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
53
|
+
export type RouteHandler = (req: Request, query: any, ...params: any[]) => any;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/decorators/controller/types/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "mergeOptions", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return mergeOptions;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _javascript = require("@impactor/javascript");
|
|
12
|
+
function mergeOptions(...options) {
|
|
13
|
+
let opts = (0, _javascript.merge)(options.map((el)=>(0, _javascript.removeValues)(el, [
|
|
14
|
+
undefined,
|
|
15
|
+
null
|
|
16
|
+
]) || {}), {
|
|
17
|
+
deep: true
|
|
18
|
+
});
|
|
19
|
+
// todo: merge matched routes (by methodNam, path, and httpMethod) and queryParams (by name)
|
|
20
|
+
// the later override the previous one
|
|
21
|
+
// opts.queryParams?.map((param) => {
|
|
22
|
+
// let existingQueryParam = opts.queryParams!.findIndex(
|
|
23
|
+
// (el) => el.name === param.name,
|
|
24
|
+
// );
|
|
25
|
+
// opts.queryParams!.push(
|
|
26
|
+
// existingQueryParam > -1
|
|
27
|
+
// ? {
|
|
28
|
+
// ...opts.queryParams![existingQueryParam],
|
|
29
|
+
// ...param,
|
|
30
|
+
// }
|
|
31
|
+
// : param,
|
|
32
|
+
// );
|
|
33
|
+
// });
|
|
34
|
+
return opts;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=merge-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/decorators/controller/utils/merge-options.ts"],"sourcesContent":["import { ControllerOptions, InitControllerOptions } from '../types';\nimport { merge, removeValues } from '@impactor/javascript';\n\nexport function mergeOptions(\n ...options: InitControllerOptions[]\n): ControllerOptions {\n let opts = <ControllerOptions>merge(\n options.map((el) => removeValues(el, [undefined, null]) || {}),\n { deep: true },\n );\n\n // todo: merge matched routes (by methodNam, path, and httpMethod) and queryParams (by name)\n // the later override the previous one\n // opts.queryParams?.map((param) => {\n // let existingQueryParam = opts.queryParams!.findIndex(\n // (el) => el.name === param.name,\n // );\n\n // opts.queryParams!.push(\n // existingQueryParam > -1\n // ? {\n // ...opts.queryParams![existingQueryParam],\n // ...param,\n // }\n // : param,\n // );\n // });\n\n return opts;\n}\n"],"names":["mergeOptions","options","opts","merge","map","el","removeValues","undefined","deep"],"mappings":";;;;+BAGgBA;;;eAAAA;;;4BAFoB;AAE7B,SAASA,aACd,GAAGC,OAAgC;IAEnC,IAAIC,OAA0BC,IAAAA,iBAAK,EACjCF,QAAQG,GAAG,CAAC,CAACC,KAAOC,IAAAA,wBAAY,EAACD,IAAI;YAACE;YAAW;SAAK,KAAK,CAAC,IAC5D;QAAEC,MAAM;IAAK;IAGf,4FAA4F;IAC5F,sCAAsC;IACtC,qCAAqC;IACrC,0DAA0D;IAC1D,sCAAsC;IACtC,OAAO;IAEP,4BAA4B;IAC5B,8BAA8B;IAC9B,YAAY;IACZ,sDAAsD;IACtD,sBAAsB;IACtB,YAAY;IACZ,iBAAiB;IACjB,OAAO;IACP,MAAM;IAEN,OAAON;AACT"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ControllerFactory } from '../controller.factory';
|
|
2
|
+
import { IRoute } from '../types';
|
|
3
|
+
export declare function has(property: string, factory: ControllerFactory, route?: IRoute): boolean;
|
|
4
|
+
export declare function get(property: string, factory: ControllerFactory, route?: IRoute): any;
|
|
5
|
+
export declare function set<T extends Object>(property: string, value: T, factory: ControllerFactory, route?: IRoute): void;
|
|
6
|
+
export declare function getKeys(factory: ControllerFactory, route?: IRoute): any[];
|
|
7
|
+
export declare function getAll(factory: ControllerFactory, route?: IRoute): {
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
export declare function applyDecorators(factory: ControllerFactory): void;
|