@koalarx/nest 1.19.0 → 3.0.2
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/CHANGELOG.md +21 -0
- package/package.json +5 -37
- package/src/core/backgroud-services/cron-service/cron-job.handler.base.ts +66 -0
- package/src/core/backgroud-services/cron-service/cron-job.handler.spec.ts +38 -0
- package/src/core/backgroud-services/event-service/event-class.ts +5 -0
- package/src/core/backgroud-services/event-service/event-handler.base.ts +17 -0
- package/src/core/backgroud-services/event-service/event-is-trigger.ts +3 -0
- package/src/core/backgroud-services/event-service/event-job.ts +28 -0
- package/src/core/backgroud-services/event-service/event-queue.spec.ts +47 -0
- package/src/core/backgroud-services/event-service/event-queue.ts +107 -0
- package/src/core/constants/query-params.ts +7 -0
- package/src/core/controllers/base.controller.ts +9 -0
- package/src/core/controllers/controller.decorator.ts +10 -0
- package/src/core/controllers/created-registre-response.base.ts +17 -0
- package/src/core/controllers/list-response.base.ts +8 -0
- package/src/core/controllers/pagination.request.ts +41 -0
- package/src/core/controllers/router-config.base.ts +14 -0
- package/src/core/controllers/schemas/boolean.schema.ts +10 -0
- package/src/core/controllers/schemas/document-number.schema.ts +23 -0
- package/src/core/controllers/schemas/email.schema.ts +13 -0
- package/src/core/controllers/schemas/list-query.schema.ts +17 -0
- package/src/core/controllers/schemas/native-enum.schema.ts +34 -0
- package/src/core/controllers/schemas/set-mask-document-number.schema.ts +13 -0
- package/src/core/database/entity.base.ts +95 -0
- package/src/core/database/entity.decorator.spec.ts +71 -0
- package/src/core/database/entity.decorator.ts +39 -0
- package/src/core/database/prisma-client-with-custom-transaction.interface.ts +13 -0
- package/src/core/database/prisma-resolver.ts +99 -0
- package/src/core/database/prisma-transactional-client.ts +43 -0
- package/src/core/database/prisma.service.ts +136 -0
- package/src/core/database/repository.base.ts +548 -0
- package/src/core/dtos/pagination.dto.ts +35 -0
- package/src/core/errors/bad-request.error.ts +8 -0
- package/src/core/errors/conflict.error.ts +7 -0
- package/src/core/errors/error.base.ts +5 -0
- package/src/core/errors/no-content.error.ts +8 -0
- package/src/core/errors/not-allowed.error.ts +8 -0
- package/src/core/errors/resource-not-found.error.ts +8 -0
- package/{core/errors/use-case-error.d.ts → src/core/errors/use-case-error.ts} +1 -1
- package/src/core/errors/user-already-exist.error.ts +7 -0
- package/src/core/errors/wrong-credentials.error.ts +7 -0
- package/src/core/health-check/health-check.controller.ts +13 -0
- package/src/core/health-check/health-check.module.ts +7 -0
- package/src/core/index.ts +56 -0
- package/src/core/koala-app.ts +379 -0
- package/src/core/koala-global-vars.ts +8 -0
- package/src/core/koala-nest-database.module.ts +65 -0
- package/src/core/koala-nest-http.module.ts +44 -0
- package/src/core/koala-nest.module.ts +67 -0
- package/src/core/mapping/auto-mapping-class-context.ts +28 -0
- package/src/core/mapping/auto-mapping-context.ts +26 -0
- package/src/core/mapping/auto-mapping-list.ts +154 -0
- package/src/core/mapping/auto-mapping-profile.ts +3 -0
- package/src/core/mapping/auto-mapping.decorator.ts +54 -0
- package/src/core/mapping/auto-mapping.module.ts +17 -0
- package/src/core/mapping/auto-mapping.service.ts +187 -0
- package/src/core/mapping/create-map.ts +11 -0
- package/src/core/mapping/for-member.ts +16 -0
- package/src/core/request-overflow/request-handler.base.ts +8 -0
- package/src/core/request-overflow/request-result.spec.ts +23 -0
- package/src/core/request-overflow/request-result.ts +41 -0
- package/src/core/request-overflow/request-validator.base.ts +33 -0
- package/src/core/security/strategies/api-key.strategy.ts +45 -0
- package/src/core/utils/assing-object.ts +9 -0
- package/src/core/utils/env.config.ts +17 -0
- package/src/core/utils/filter-request-params.ts +23 -0
- package/src/core/utils/find-on-list.ts +18 -0
- package/src/core/utils/get-type-by-prop.ts +9 -0
- package/src/core/utils/instanciate-class-with-dependencies-injection.ts +12 -0
- package/src/core/utils/interfaces/icomparable.ts +6 -0
- package/src/core/utils/list.spec.ts +81 -0
- package/src/core/utils/list.ts +223 -0
- package/src/core/utils/promise-all.ts +24 -0
- package/src/core/utils/set-mask-document-number.ts +13 -0
- package/src/core/validators/file-validator.ts +113 -0
- package/src/decorators/api-exclude-endpoint-diff-develop.decorator.ts +15 -0
- package/src/decorators/api-property-enum.decorator.ts +58 -0
- package/src/decorators/api-property-only-develop.decorator.ts +6 -0
- package/src/decorators/cookies.decorator.ts +8 -0
- package/src/decorators/is-public.decorator.ts +5 -0
- package/src/decorators/upload.decorator.ts +31 -0
- package/src/env/env.module.ts +8 -0
- package/src/env/env.service.ts +12 -0
- package/src/env/env.ts +14 -0
- package/src/filters/domain-errors.filter.ts +97 -0
- package/src/filters/global-exception.filter.ts +60 -0
- package/src/filters/prisma-validation-exception.filter.ts +73 -0
- package/src/filters/zod-errors.filter.ts +48 -0
- package/src/services/logging/ilogging.service.ts +17 -0
- package/src/services/logging/logging.service.ts +10 -0
- package/src/services/redis/iredis.service.ts +11 -0
- package/src/services/redis/redis.service.ts +70 -0
- package/src/services/redlock/ired-lock.service.ts +4 -0
- package/src/services/redlock/red-lock.service.ts +36 -0
- package/src/test/koala-app-test-dependencies.ts +15 -0
- package/src/test/koala-app-test.ts +103 -0
- package/src/test/repositories/in-memory-base.repository.ts +90 -0
- package/src/test/services/fake-logging.service.ts +7 -0
- package/src/test/services/fake-red-lock.service.ts +11 -0
- package/src/test/utils/create-e2e-database.ts +55 -0
- package/src/test/utils/drop-e2e-database.ts +36 -0
- package/src/test/utils/wait-for.ts +31 -0
- package/tsconfig.lib.json +11 -0
- package/LICENSE +0 -21
- package/README.md +0 -499
- package/core/backgroud-services/cron-service/cron-job.handler.base.d.ts +0 -16
- package/core/backgroud-services/cron-service/cron-job.handler.base.js +0 -49
- package/core/backgroud-services/event-service/event-class.d.ts +0 -5
- package/core/backgroud-services/event-service/event-class.js +0 -11
- package/core/backgroud-services/event-service/event-handler.base.d.ts +0 -8
- package/core/backgroud-services/event-service/event-handler.base.js +0 -14
- package/core/backgroud-services/event-service/event-is-trigger.d.ts +0 -3
- package/core/backgroud-services/event-service/event-is-trigger.js +0 -7
- package/core/backgroud-services/event-service/event-job.d.ts +0 -13
- package/core/backgroud-services/event-service/event-job.js +0 -21
- package/core/backgroud-services/event-service/event-queue.d.ts +0 -17
- package/core/backgroud-services/event-service/event-queue.js +0 -62
- package/core/constants/query-params.d.ts +0 -6
- package/core/constants/query-params.js +0 -8
- package/core/controllers/base.controller.d.ts +0 -4
- package/core/controllers/base.controller.js +0 -6
- package/core/controllers/controller.decorator.d.ts +0 -2
- package/core/controllers/controller.decorator.js +0 -11
- package/core/controllers/created-registre-response.base.d.ts +0 -10
- package/core/controllers/created-registre-response.base.js +0 -35
- package/core/controllers/list-response.base.d.ts +0 -4
- package/core/controllers/list-response.base.js +0 -21
- package/core/controllers/pagination.request.d.ts +0 -10
- package/core/controllers/pagination.request.js +0 -56
- package/core/controllers/router-config.base.d.ts +0 -7
- package/core/controllers/router-config.base.js +0 -18
- package/core/controllers/schemas/boolean.schema.d.ts +0 -2
- package/core/controllers/schemas/boolean.schema.js +0 -12
- package/core/controllers/schemas/document-number.schema.d.ts +0 -1
- package/core/controllers/schemas/document-number.schema.js +0 -26
- package/core/controllers/schemas/email.schema.d.ts +0 -1
- package/core/controllers/schemas/email.schema.js +0 -13
- package/core/controllers/schemas/list-query.schema.d.ts +0 -17
- package/core/controllers/schemas/list-query.schema.js +0 -19
- package/core/controllers/schemas/native-enum.schema.d.ts +0 -7
- package/core/controllers/schemas/native-enum.schema.js +0 -28
- package/core/controllers/schemas/set-mask-document-number.schema.d.ts +0 -1
- package/core/controllers/schemas/set-mask-document-number.schema.js +0 -13
- package/core/database/entity.base.d.ts +0 -20
- package/core/database/entity.base.js +0 -71
- package/core/database/entity.decorator.d.ts +0 -13
- package/core/database/entity.decorator.js +0 -23
- package/core/database/prisma-client-with-custom-transaction.interface.d.ts +0 -8
- package/core/database/prisma-client-with-custom-transaction.interface.js +0 -2
- package/core/database/prisma-resolver.d.ts +0 -2
- package/core/database/prisma-resolver.js +0 -74
- package/core/database/prisma-transactional-client.d.ts +0 -11
- package/core/database/prisma-transactional-client.js +0 -25
- package/core/database/prisma.service.d.ts +0 -24
- package/core/database/prisma.service.js +0 -104
- package/core/database/repository.base.d.ts +0 -44
- package/core/database/repository.base.js +0 -360
- package/core/dtos/pagination.dto.d.ts +0 -9
- package/core/dtos/pagination.dto.js +0 -49
- package/core/errors/bad-request.error.d.ts +0 -5
- package/core/errors/bad-request.error.js +0 -10
- package/core/errors/conflict.error.d.ts +0 -4
- package/core/errors/conflict.error.js +0 -10
- package/core/errors/error.base.d.ts +0 -4
- package/core/errors/error.base.js +0 -11
- package/core/errors/no-content.error.d.ts +0 -5
- package/core/errors/no-content.error.js +0 -10
- package/core/errors/not-allowed.error.d.ts +0 -5
- package/core/errors/not-allowed.error.js +0 -10
- package/core/errors/resource-not-found.error.d.ts +0 -5
- package/core/errors/resource-not-found.error.js +0 -10
- package/core/errors/use-case-error.js +0 -2
- package/core/errors/user-already-exist.error.d.ts +0 -4
- package/core/errors/user-already-exist.error.js +0 -10
- package/core/errors/wrong-credentials.error.d.ts +0 -4
- package/core/errors/wrong-credentials.error.js +0 -10
- package/core/health-check/health-check.controller.d.ts +0 -5
- package/core/health-check/health-check.controller.js +0 -32
- package/core/health-check/health-check.module.d.ts +0 -2
- package/core/health-check/health-check.module.js +0 -19
- package/core/index.d.ts +0 -18
- package/core/index.js +0 -7
- package/core/koala-app.d.ts +0 -64
- package/core/koala-app.js +0 -252
- package/core/koala-global-vars.d.ts +0 -7
- package/core/koala-global-vars.js +0 -9
- package/core/koala-nest-database.module.d.ts +0 -16
- package/core/koala-nest-database.module.js +0 -52
- package/core/koala-nest-http.module.d.ts +0 -13
- package/core/koala-nest-http.module.js +0 -37
- package/core/koala-nest.module.d.ts +0 -17
- package/core/koala-nest.module.js +0 -66
- package/core/mapping/auto-mapping-class-context.d.ts +0 -16
- package/core/mapping/auto-mapping-class-context.js +0 -18
- package/core/mapping/auto-mapping-context.d.ts +0 -11
- package/core/mapping/auto-mapping-context.js +0 -24
- package/core/mapping/auto-mapping-list.d.ts +0 -27
- package/core/mapping/auto-mapping-list.js +0 -94
- package/core/mapping/auto-mapping-profile.d.ts +0 -3
- package/core/mapping/auto-mapping-profile.js +0 -6
- package/core/mapping/auto-mapping.decorator.d.ts +0 -9
- package/core/mapping/auto-mapping.decorator.js +0 -27
- package/core/mapping/auto-mapping.module.d.ts +0 -5
- package/core/mapping/auto-mapping.module.js +0 -29
- package/core/mapping/auto-mapping.service.d.ts +0 -14
- package/core/mapping/auto-mapping.service.js +0 -140
- package/core/mapping/create-map.d.ts +0 -3
- package/core/mapping/create-map.js +0 -7
- package/core/mapping/for-member.d.ts +0 -5
- package/core/mapping/for-member.js +0 -8
- package/core/request-overflow/request-handler.base.d.ts +0 -4
- package/core/request-overflow/request-handler.base.js +0 -6
- package/core/request-overflow/request-result.d.ts +0 -15
- package/core/request-overflow/request-result.js +0 -37
- package/core/request-overflow/request-validator.base.d.ts +0 -7
- package/core/request-overflow/request-validator.base.js +0 -26
- package/core/security/strategies/api-key.strategy.d.ts +0 -16
- package/core/security/strategies/api-key.strategy.js +0 -31
- package/core/utils/assing-object.d.ts +0 -5
- package/core/utils/assing-object.js +0 -6
- package/core/utils/env.config.d.ts +0 -6
- package/core/utils/env.config.js +0 -18
- package/core/utils/filter-request-params.d.ts +0 -13
- package/core/utils/filter-request-params.js +0 -22
- package/core/utils/find-on-list.d.ts +0 -2
- package/core/utils/find-on-list.js +0 -13
- package/core/utils/get-type-by-prop.d.ts +0 -2
- package/core/utils/get-type-by-prop.js +0 -11
- package/core/utils/instanciate-class-with-dependencies-injection.d.ts +0 -2
- package/core/utils/instanciate-class-with-dependencies-injection.js +0 -10
- package/core/utils/interfaces/icomparable.d.ts +0 -5
- package/core/utils/interfaces/icomparable.js +0 -6
- package/core/utils/list.d.ts +0 -39
- package/core/utils/list.js +0 -168
- package/core/utils/promise-all.d.ts +0 -7
- package/core/utils/promise-all.js +0 -19
- package/core/utils/set-mask-document-number.d.ts +0 -1
- package/core/utils/set-mask-document-number.js +0 -13
- package/core/validators/file-validator.d.ts +0 -27
- package/core/validators/file-validator.js +0 -94
- package/decorators/api-exclude-endpoint-diff-develop.decorator.d.ts +0 -1
- package/decorators/api-exclude-endpoint-diff-develop.decorator.js +0 -9
- package/decorators/api-property-enum.decorator.d.ts +0 -8
- package/decorators/api-property-enum.decorator.js +0 -21
- package/decorators/api-property-only-develop.decorator.d.ts +0 -2
- package/decorators/api-property-only-develop.decorator.js +0 -9
- package/decorators/cookies.decorator.d.ts +0 -1
- package/decorators/cookies.decorator.js +0 -8
- package/decorators/is-public.decorator.d.ts +0 -2
- package/decorators/is-public.decorator.js +0 -7
- package/decorators/upload.decorator.d.ts +0 -1
- package/decorators/upload.decorator.js +0 -18
- package/docs/00-cli-reference.md +0 -201
- package/docs/01-guia-instalacao.md +0 -113
- package/docs/02-configuracao-inicial.md +0 -176
- package/docs/04-tratamento-erros.md +0 -303
- package/docs/05-features-avancadas.md +0 -969
- package/docs/06-decoradores.md +0 -220
- package/docs/07-guia-bun.md +0 -176
- package/docs/08-prisma-client.md +0 -487
- package/docs/09-mcp-vscode-extension.md +0 -437
- package/docs/EXAMPLE.md +0 -1671
- package/docs/README.md +0 -59
- package/env/env.d.ts +0 -25
- package/env/env.js +0 -14
- package/env/env.module.d.ts +0 -2
- package/env/env.module.js +0 -20
- package/env/env.service.d.ts +0 -7
- package/env/env.service.js +0 -28
- package/filters/domain-errors.filter.d.ts +0 -18
- package/filters/domain-errors.filter.js +0 -92
- package/filters/global-exception.filter.d.ts +0 -8
- package/filters/global-exception.filter.js +0 -68
- package/filters/prisma-validation-exception.filter.d.ts +0 -10
- package/filters/prisma-validation-exception.filter.js +0 -82
- package/filters/zod-errors.filter.d.ts +0 -9
- package/filters/zod-errors.filter.js +0 -60
- package/mcp-server/mcp.json.example +0 -27
- package/mcp-server/server.d.ts +0 -2
- package/mcp-server/server.d.ts.map +0 -1
- package/mcp-server/server.js +0 -248
- package/mcp-server/server.js.map +0 -1
- package/services/logging/ilogging.service.d.ts +0 -16
- package/services/logging/ilogging.service.js +0 -6
- package/services/logging/logging.service.d.ts +0 -4
- package/services/logging/logging.service.js +0 -20
- package/services/redis/iredis.service.d.ts +0 -6
- package/services/redis/iredis.service.js +0 -6
- package/services/redis/redis.service.d.ts +0 -14
- package/services/redis/redis.service.js +0 -65
- package/services/redlock/ired-lock.service.d.ts +0 -4
- package/services/redlock/ired-lock.service.js +0 -6
- package/services/redlock/red-lock.service.d.ts +0 -9
- package/services/redlock/red-lock.service.js +0 -46
- package/test/koala-app-test-dependencies.d.ts +0 -10
- package/test/koala-app-test-dependencies.js +0 -13
- package/test/koala-app-test.d.ts +0 -22
- package/test/koala-app-test.js +0 -77
- package/test/repositories/in-memory-base.repository.d.ts +0 -17
- package/test/repositories/in-memory-base.repository.js +0 -65
- package/test/services/fake-logging.service.d.ts +0 -4
- package/test/services/fake-logging.service.js +0 -9
- package/test/services/fake-red-lock.service.d.ts +0 -5
- package/test/services/fake-red-lock.service.js +0 -11
- package/test/utils/create-e2e-database.d.ts +0 -2
- package/test/utils/create-e2e-database.js +0 -47
- package/test/utils/drop-e2e-database.d.ts +0 -2
- package/test/utils/drop-e2e-database.js +0 -33
- package/test/utils/wait-for.d.ts +0 -1
- package/test/utils/wait-for.js +0 -21
- package/tsconfig.lib.tsbuildinfo +0 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Type } from '@nestjs/common'
|
|
2
|
+
import { findOnList } from '../utils/find-on-list'
|
|
3
|
+
import { getTypeByProp } from '../utils/get-type-by-prop'
|
|
4
|
+
import { List } from '../utils/list'
|
|
5
|
+
import { AutoMappingClassContext } from './auto-mapping-class-context'
|
|
6
|
+
import { AutoMappingContext } from './auto-mapping-context'
|
|
7
|
+
import { ForMemberDefinition } from './for-member'
|
|
8
|
+
|
|
9
|
+
interface AutoMappingGetContext {
|
|
10
|
+
mapContext: AutoMappingContext | null
|
|
11
|
+
propSourceContext: AutoMappingClassContext | null
|
|
12
|
+
propTargetContext: AutoMappingClassContext | null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class AutoMappingList {
|
|
16
|
+
private static _mappedPropList: List<AutoMappingClassContext> | undefined
|
|
17
|
+
private static _mappingProfileList: List<AutoMappingContext> | undefined
|
|
18
|
+
|
|
19
|
+
private static initializeLists() {
|
|
20
|
+
if (!this._mappedPropList) {
|
|
21
|
+
this._mappedPropList = new List(AutoMappingClassContext)
|
|
22
|
+
}
|
|
23
|
+
if (!this._mappingProfileList) {
|
|
24
|
+
this._mappingProfileList = new List(AutoMappingContext)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static add(
|
|
29
|
+
source: Type<any>,
|
|
30
|
+
target: Type<any>,
|
|
31
|
+
...forMember: ForMemberDefinition<any, any>
|
|
32
|
+
) {
|
|
33
|
+
this.initializeLists()
|
|
34
|
+
this._mappingProfileList!.add(
|
|
35
|
+
new AutoMappingContext(source, target, forMember),
|
|
36
|
+
)
|
|
37
|
+
this._mappedPropList!.add(new AutoMappingClassContext(source))
|
|
38
|
+
this._mappedPropList!.add(new AutoMappingClassContext(target))
|
|
39
|
+
|
|
40
|
+
this.addExtendedPropsIntoSubClass(source)
|
|
41
|
+
this.addExtendedPropsIntoSubClass(target)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static get(source: Type<any>, target: Type<any>): AutoMappingGetContext {
|
|
45
|
+
this.initializeLists()
|
|
46
|
+
return {
|
|
47
|
+
mapContext: findOnList(
|
|
48
|
+
this._mappingProfileList!,
|
|
49
|
+
(mp) =>
|
|
50
|
+
mp.source.name === source.name && mp.target.name === target.name,
|
|
51
|
+
(mp) =>
|
|
52
|
+
Object.getPrototypeOf(mp.source.prototype.constructor) === source &&
|
|
53
|
+
mp.target.name === target.name,
|
|
54
|
+
),
|
|
55
|
+
propSourceContext: this._mappedPropList!.find(
|
|
56
|
+
(mp) => mp.source.name === source.name,
|
|
57
|
+
),
|
|
58
|
+
propTargetContext: this._mappedPropList!.find(
|
|
59
|
+
(mp) => mp.source.name === target.name,
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static getSourceByName(sourceName: string) {
|
|
65
|
+
this.initializeLists()
|
|
66
|
+
return this._mappedPropList!.find((mp) => mp.source.name === sourceName)
|
|
67
|
+
?.source
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static getPropDefinitions(source: Type<any>, propName: string) {
|
|
71
|
+
this.initializeLists()
|
|
72
|
+
const prop = this._mappedPropList!.find(
|
|
73
|
+
(mp) => mp.source.name === source.name,
|
|
74
|
+
)?.props.find((prop) => prop.name === propName)
|
|
75
|
+
|
|
76
|
+
if (!prop) {
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
...prop,
|
|
82
|
+
type: getTypeByProp(prop),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static getTargets(source: Type<any>) {
|
|
87
|
+
this.initializeLists()
|
|
88
|
+
return this._mappingProfileList!.filter(
|
|
89
|
+
(mp) =>
|
|
90
|
+
mp.source.name === source.name ||
|
|
91
|
+
Object.getPrototypeOf(mp.source.prototype.constructor) === source,
|
|
92
|
+
)
|
|
93
|
+
.map((mp) => mp.target)
|
|
94
|
+
.toArray()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static addMappedProp(source: Type<any>, propName: string) {
|
|
98
|
+
this.initializeLists()
|
|
99
|
+
let mappedClass = this._mappedPropList!.find(
|
|
100
|
+
(mp) => mp.source.name === source.name,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if (!mappedClass) {
|
|
104
|
+
mappedClass = new AutoMappingClassContext(source)
|
|
105
|
+
|
|
106
|
+
const mappedExtendedClass = this._mappedPropList!.find(
|
|
107
|
+
(mp) =>
|
|
108
|
+
mp.source === Object.getPrototypeOf(source.prototype.constructor),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if (mappedExtendedClass) {
|
|
112
|
+
mappedClass.props.setList(mappedExtendedClass.props.toArray())
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this._mappedPropList!.add(mappedClass)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const type = Reflect.getMetadata('design:type', source.prototype, propName)
|
|
119
|
+
const compositionType = Reflect.getMetadata(
|
|
120
|
+
'composition:type',
|
|
121
|
+
source.prototype,
|
|
122
|
+
propName,
|
|
123
|
+
)
|
|
124
|
+
const compositionAction = Reflect.getMetadata(
|
|
125
|
+
'composition:action',
|
|
126
|
+
source.prototype,
|
|
127
|
+
propName,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
mappedClass.props.add({
|
|
131
|
+
name: propName,
|
|
132
|
+
type,
|
|
133
|
+
compositionType,
|
|
134
|
+
compositionAction,
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static addExtendedPropsIntoSubClass(source: Type<any>) {
|
|
139
|
+
this.initializeLists()
|
|
140
|
+
const mappedExtendedClass = this._mappedPropList!.find(
|
|
141
|
+
(mp) => mp.source === Object.getPrototypeOf(source.prototype.constructor),
|
|
142
|
+
)
|
|
143
|
+
if (mappedExtendedClass) {
|
|
144
|
+
const mappedClass = this._mappedPropList!.find(
|
|
145
|
+
(mp) => mp.source.name === source.name,
|
|
146
|
+
)
|
|
147
|
+
if (mappedClass) {
|
|
148
|
+
mappedExtendedClass.props
|
|
149
|
+
.toArray()
|
|
150
|
+
.forEach((prop) => mappedClass.props.add(prop))
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Type } from '@nestjs/common'
|
|
2
|
+
import { AutoMappingList } from './auto-mapping-list'
|
|
3
|
+
|
|
4
|
+
interface AutoMapConfig<T> {
|
|
5
|
+
type?: () => Type<T>
|
|
6
|
+
isArray?: boolean | { addTo: boolean }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function AutoMap<T>(config?: AutoMapConfig<T>) {
|
|
10
|
+
return function (target: any, propertyKey: string) {
|
|
11
|
+
const isArray = config?.isArray
|
|
12
|
+
|
|
13
|
+
let customMetadata: (() => any) | undefined = config?.type
|
|
14
|
+
|
|
15
|
+
if (!customMetadata) {
|
|
16
|
+
customMetadata = isArray ? Array : undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (customMetadata) {
|
|
20
|
+
if (isArray) {
|
|
21
|
+
Reflect.defineMetadata(
|
|
22
|
+
'composition:type',
|
|
23
|
+
customMetadata,
|
|
24
|
+
target,
|
|
25
|
+
propertyKey,
|
|
26
|
+
)
|
|
27
|
+
Reflect.defineMetadata(
|
|
28
|
+
'composition:action',
|
|
29
|
+
`${isArray === true || !isArray.addTo ? 'onlySet' : 'addTo'}`,
|
|
30
|
+
target,
|
|
31
|
+
propertyKey,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if (customMetadata !== Array) {
|
|
35
|
+
customMetadata = Array
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
!Reflect.getMetadata('design:type', target, propertyKey) ||
|
|
41
|
+
!isArray
|
|
42
|
+
) {
|
|
43
|
+
Reflect.defineMetadata(
|
|
44
|
+
'design:type',
|
|
45
|
+
customMetadata,
|
|
46
|
+
target,
|
|
47
|
+
propertyKey,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
AutoMappingList.addMappedProp(target.constructor, propertyKey)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DynamicModule, Module, Type } from '@nestjs/common'
|
|
2
|
+
import { AutoMappingProfile } from './auto-mapping-profile'
|
|
3
|
+
import { AutoMappingService } from './auto-mapping.service'
|
|
4
|
+
|
|
5
|
+
@Module({})
|
|
6
|
+
export class AutoMappingModule {
|
|
7
|
+
static register(profile: Type<AutoMappingProfile>): DynamicModule {
|
|
8
|
+
return {
|
|
9
|
+
module: AutoMappingModule,
|
|
10
|
+
providers: [
|
|
11
|
+
{ provide: AutoMappingProfile, useClass: profile },
|
|
12
|
+
AutoMappingService,
|
|
13
|
+
],
|
|
14
|
+
exports: [AutoMappingService],
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { Injectable, Type } from '@nestjs/common'
|
|
2
|
+
import { getTypeByProp } from '../utils/get-type-by-prop'
|
|
3
|
+
import { List } from '../utils/list'
|
|
4
|
+
import { AutoMappingList } from './auto-mapping-list'
|
|
5
|
+
import { AutoMappingProfile } from './auto-mapping-profile'
|
|
6
|
+
|
|
7
|
+
@Injectable()
|
|
8
|
+
export class AutoMappingService {
|
|
9
|
+
private readonly _contextList = AutoMappingList
|
|
10
|
+
|
|
11
|
+
constructor(automappingProfile: AutoMappingProfile) {
|
|
12
|
+
automappingProfile.profile()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private getInstanceType(
|
|
16
|
+
prop: () => Type<any> | ArrayConstructor,
|
|
17
|
+
): Type<any> | ArrayConstructor {
|
|
18
|
+
try {
|
|
19
|
+
return prop() ?? prop
|
|
20
|
+
} catch {
|
|
21
|
+
return prop as any
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
map<S, T>(data: any, source: Type<S>, target: Type<T>): T {
|
|
26
|
+
const { mapContext, propSourceContext, propTargetContext } =
|
|
27
|
+
this._contextList.get(source, target)
|
|
28
|
+
|
|
29
|
+
if (!mapContext) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`No mapping context found for ${source.name} to ${target.name}`,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const mappedTarget = new target.prototype.constructor()
|
|
36
|
+
|
|
37
|
+
propSourceContext?.props.forEach((propSource) => {
|
|
38
|
+
const value = data[propSource.name]
|
|
39
|
+
const compositionType = propSource.compositionType
|
|
40
|
+
const compositionAction = propSource.compositionAction
|
|
41
|
+
|
|
42
|
+
if (value !== undefined) {
|
|
43
|
+
const targetProp = propTargetContext?.props.find(
|
|
44
|
+
(tp) => tp.name === propSource.name,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if (targetProp) {
|
|
48
|
+
const typeSource = getTypeByProp(propSource)
|
|
49
|
+
const typeTarget = getTypeByProp(targetProp)
|
|
50
|
+
|
|
51
|
+
const listToArray =
|
|
52
|
+
typeSource === List.name &&
|
|
53
|
+
typeTarget === Array.name &&
|
|
54
|
+
value instanceof List
|
|
55
|
+
|
|
56
|
+
const arrayToList =
|
|
57
|
+
typeSource === Array.name &&
|
|
58
|
+
typeTarget === List.name &&
|
|
59
|
+
value instanceof Array &&
|
|
60
|
+
!!compositionType
|
|
61
|
+
|
|
62
|
+
const arrayToArray =
|
|
63
|
+
typeSource === Array.name &&
|
|
64
|
+
typeTarget === Array.name &&
|
|
65
|
+
value instanceof Array &&
|
|
66
|
+
!!compositionType
|
|
67
|
+
|
|
68
|
+
let mappedValue = value
|
|
69
|
+
|
|
70
|
+
if (listToArray) {
|
|
71
|
+
mappedValue = this.mapListToArray(value)
|
|
72
|
+
} else if (arrayToList) {
|
|
73
|
+
mappedValue = this.mapArrayToList(
|
|
74
|
+
value,
|
|
75
|
+
this.getInstanceType(compositionType),
|
|
76
|
+
compositionAction === 'onlySet',
|
|
77
|
+
)
|
|
78
|
+
} else if (arrayToArray) {
|
|
79
|
+
mappedValue = this.mapArrayToArray(
|
|
80
|
+
value,
|
|
81
|
+
this.getInstanceType(compositionType),
|
|
82
|
+
)
|
|
83
|
+
} else {
|
|
84
|
+
const propSourceInstance =
|
|
85
|
+
this._contextList.getSourceByName(typeSource)
|
|
86
|
+
if (propSourceInstance) {
|
|
87
|
+
mappedValue = this.mapNestedProp(value, propSourceInstance)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
mappedTarget[targetProp.name] = mappedValue
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
propTargetContext?.props.forEach((prop) => {
|
|
97
|
+
const formMemberDefinition = mapContext.forMemberDifinitions?.find(
|
|
98
|
+
(def) => def[prop.name],
|
|
99
|
+
)?.[prop.name]
|
|
100
|
+
|
|
101
|
+
if (formMemberDefinition) {
|
|
102
|
+
mappedTarget[prop.name] = formMemberDefinition(data)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return mappedTarget
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private getTarget(data: any, source: Type<any>) {
|
|
110
|
+
if (this.isPrimitiveType(data)) {
|
|
111
|
+
return data
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const targets = this._contextList.getTargets(source.prototype.constructor)
|
|
115
|
+
|
|
116
|
+
if (targets.length === 0) {
|
|
117
|
+
throw new Error(`No mapping context found for ${source.name}`)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return targets[0]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private mapNestedProp(data: any, source: Type<any>) {
|
|
124
|
+
return this.map(
|
|
125
|
+
data,
|
|
126
|
+
source.prototype.constructor,
|
|
127
|
+
this.getTarget(data, source),
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private mapListToArray(value: List<any>) {
|
|
132
|
+
return value.toArray().map((item) => {
|
|
133
|
+
const entityOnList = value.entityType?.prototype.constructor
|
|
134
|
+
|
|
135
|
+
if (entityOnList) {
|
|
136
|
+
return this.mapNestedProp(item, entityOnList) ?? {}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {}
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private mapArrayToList(
|
|
144
|
+
value: Array<any>,
|
|
145
|
+
compositionType: Type<any>,
|
|
146
|
+
onlySet = true,
|
|
147
|
+
) {
|
|
148
|
+
const list = new List(
|
|
149
|
+
this.getTarget(value, compositionType.prototype.constructor),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const mappedValue = value.map(
|
|
153
|
+
(item) =>
|
|
154
|
+
this.mapNestedProp(item, compositionType.prototype.constructor) ?? {},
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if (onlySet) {
|
|
158
|
+
list.setList(mappedValue)
|
|
159
|
+
} else {
|
|
160
|
+
list.update(mappedValue)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return list
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private mapArrayToArray(value: Array<any>, compositionType: Type<any>) {
|
|
167
|
+
return value.map(
|
|
168
|
+
(item) =>
|
|
169
|
+
this.mapNestedProp(item, compositionType.prototype.constructor) ?? {},
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private isPrimitiveType(value: any) {
|
|
174
|
+
switch (typeof value) {
|
|
175
|
+
case 'string':
|
|
176
|
+
case 'number':
|
|
177
|
+
case 'bigint':
|
|
178
|
+
case 'boolean':
|
|
179
|
+
return true
|
|
180
|
+
case 'symbol':
|
|
181
|
+
case 'undefined':
|
|
182
|
+
case 'object':
|
|
183
|
+
case 'function':
|
|
184
|
+
return false
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Type } from '@nestjs/common'
|
|
2
|
+
import { AutoMappingList } from './auto-mapping-list'
|
|
3
|
+
import { ForMemberDefinition } from './for-member'
|
|
4
|
+
|
|
5
|
+
export function createMap<TSource, TTarget>(
|
|
6
|
+
source: Type<TSource>,
|
|
7
|
+
target: Type<TTarget>,
|
|
8
|
+
...formMember: ForMemberDefinition<TTarget, TSource>
|
|
9
|
+
) {
|
|
10
|
+
AutoMappingList.add(source, target, ...formMember)
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ForMemberResult<TTarget, TSource> = {
|
|
2
|
+
[Key in keyof TTarget]: (source: TSource) => TTarget[Key]
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export type ForMemberDefinition<TTarget, TSource> = Array<
|
|
6
|
+
Partial<ForMemberResult<TTarget, TSource>>
|
|
7
|
+
>
|
|
8
|
+
|
|
9
|
+
export function forMember<TTarget, TSource>(
|
|
10
|
+
targetProp: keyof TTarget,
|
|
11
|
+
map: (source: TSource) => TTarget[keyof TTarget],
|
|
12
|
+
): Partial<ForMemberResult<TTarget, TSource>> {
|
|
13
|
+
return {
|
|
14
|
+
[targetProp]: map,
|
|
15
|
+
} as Partial<ForMemberResult<TTarget, TSource>>
|
|
16
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RequestResult, failure, ok } from './request-result'
|
|
2
|
+
|
|
3
|
+
function doSometing(shouldSuccess: boolean): RequestResult<string, number> {
|
|
4
|
+
if (shouldSuccess) {
|
|
5
|
+
return ok(10)
|
|
6
|
+
} else {
|
|
7
|
+
return failure('error')
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
test('success result', () => {
|
|
12
|
+
const result = doSometing(true)
|
|
13
|
+
|
|
14
|
+
expect(result.isOk()).toBe(true)
|
|
15
|
+
expect(result.isFailure()).toBe(false)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('error result', () => {
|
|
19
|
+
const result = doSometing(false)
|
|
20
|
+
|
|
21
|
+
expect(result.isFailure()).toBe(true)
|
|
22
|
+
expect(result.isOk()).toBe(false)
|
|
23
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export class Failure<L, R> {
|
|
2
|
+
readonly value: L
|
|
3
|
+
|
|
4
|
+
constructor(value: L) {
|
|
5
|
+
this.value = value
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
isFailure(): this is Failure<L, R> {
|
|
9
|
+
return true
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
isOk(): this is Ok<L, R> {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class Ok<L, R> {
|
|
18
|
+
readonly value: R
|
|
19
|
+
|
|
20
|
+
constructor(value: R) {
|
|
21
|
+
this.value = value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
isFailure(): this is Failure<L, R> {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
isOk(): this is Ok<L, R> {
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type RequestResult<L, R> = Failure<L, R> | Ok<L, R>
|
|
34
|
+
|
|
35
|
+
export const failure = <L, R>(value: L): RequestResult<L, R> => {
|
|
36
|
+
return new Failure(value)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const ok = <L, R>(value: R): RequestResult<L, R> => {
|
|
40
|
+
return new Ok(value)
|
|
41
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ZodType } from 'zod'
|
|
2
|
+
|
|
3
|
+
export abstract class RequestValidatorBase<
|
|
4
|
+
TRequest extends Record<string, any>,
|
|
5
|
+
> {
|
|
6
|
+
protected _request: Record<string, any>
|
|
7
|
+
|
|
8
|
+
constructor(request: TRequest) {
|
|
9
|
+
this._request = { ...request }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
validate(): TRequest {
|
|
13
|
+
Object.keys(this._request).forEach((key) => {
|
|
14
|
+
if (key.includes('[]')) {
|
|
15
|
+
const newKey = key.replace('[]', '')
|
|
16
|
+
const value = this._request[key]
|
|
17
|
+
this._request[newKey] =
|
|
18
|
+
typeof value === 'string' ? value.split(',') : value
|
|
19
|
+
delete this._request[key]
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const requestParsed = this.schema.safeParse(this._request)
|
|
24
|
+
|
|
25
|
+
if (requestParsed.success) {
|
|
26
|
+
return Object.assign(this._request as any, requestParsed.data)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
throw requestParsed.error
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protected abstract get schema(): ZodType
|
|
33
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { UnauthorizedException } from '@nestjs/common'
|
|
2
|
+
import { Request } from 'express'
|
|
3
|
+
import { Strategy } from 'passport-custom'
|
|
4
|
+
|
|
5
|
+
export type DoneFn = (err: Error | null, user?: any) => void
|
|
6
|
+
|
|
7
|
+
interface ApiKeyStrategyOptions {
|
|
8
|
+
header: string
|
|
9
|
+
prefix?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
abstract class ApiKeyStrategyBase extends Strategy {
|
|
13
|
+
constructor({ header, prefix }: ApiKeyStrategyOptions) {
|
|
14
|
+
super(async (request: Request, done: DoneFn) => {
|
|
15
|
+
try {
|
|
16
|
+
const apikey = request.headers[header.toLowerCase()] as string
|
|
17
|
+
const apiKeyEncoded = apikey?.replace(`${prefix || ''} `, '')
|
|
18
|
+
|
|
19
|
+
if (apiKeyEncoded) {
|
|
20
|
+
return await this.validate(apiKeyEncoded, done, request)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return done(new UnauthorizedException())
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return done(err, null)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
abstract validate(
|
|
31
|
+
apikey: string,
|
|
32
|
+
done: DoneFn,
|
|
33
|
+
request: Request,
|
|
34
|
+
): Promise<void> | void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class ApiKeyStrategy extends ApiKeyStrategyBase {
|
|
38
|
+
constructor(options: ApiKeyStrategyOptions) {
|
|
39
|
+
super(options)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
validate(apikey: string, done: DoneFn, request: Request) {
|
|
43
|
+
throw new Error('Method not implemented.')
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Type } from '@nestjs/common'
|
|
2
|
+
|
|
3
|
+
export type ObjectProps<T> = {
|
|
4
|
+
[K in keyof T as T[K] extends Function ? never : K]: T[K]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function assignObject<T>(Target: Type<T>, source: ObjectProps<T>): T {
|
|
8
|
+
return Object.assign(new Target() as any, source)
|
|
9
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class EnvConfig {
|
|
2
|
+
static get isEnvTest() {
|
|
3
|
+
return process.env.NODE_ENV === 'test'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
static get isEnvDevelop() {
|
|
7
|
+
return process.env.NODE_ENV === 'develop'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static get isEnvStaging() {
|
|
11
|
+
return process.env.NODE_ENV === 'staging'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static get isEnvProduction() {
|
|
15
|
+
return process.env.NODE_ENV === 'production'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ArgumentsHost } from '@nestjs/common/interfaces'
|
|
2
|
+
import { Request, Response } from 'express'
|
|
3
|
+
|
|
4
|
+
export class FilterRequestParams {
|
|
5
|
+
static get(host: ArgumentsHost) {
|
|
6
|
+
const ctx = host.switchToHttp()
|
|
7
|
+
const response = ctx.getResponse<Response>()
|
|
8
|
+
const request = ctx.getRequest<Request & { user?: { login: string } }>()
|
|
9
|
+
const method = request.method
|
|
10
|
+
const isGetMethod = method === 'GET'
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
response,
|
|
14
|
+
loggedUserName:
|
|
15
|
+
request?.user?.login ?? request.headers.origin ?? request.ip ?? '',
|
|
16
|
+
filterParams: {
|
|
17
|
+
method,
|
|
18
|
+
payload: !isGetMethod ? request.body : undefined,
|
|
19
|
+
endpoint: request.url,
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { List } from './list'
|
|
2
|
+
|
|
3
|
+
export function findOnList<T>(
|
|
4
|
+
list: List<T>,
|
|
5
|
+
...predicates: ((item: T) => boolean)[]
|
|
6
|
+
) {
|
|
7
|
+
let result: T | null = null
|
|
8
|
+
|
|
9
|
+
for (const predicate of predicates) {
|
|
10
|
+
result = list.find(predicate)
|
|
11
|
+
|
|
12
|
+
if (result) {
|
|
13
|
+
break
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return result
|
|
18
|
+
}
|